Add baseline performance and allocation tests for scheduling tasks and executing (#2009)

### Motivation:

In issue https://github.com/apple/swift-nio/issues/1316, we see a large number of allocations to happen when scheduling tasks. This can definitely be optimized. This PR adds a number of baseline allocation and performance tests for both `scheduleTask` and `execute`. In the next PRs, I am going to try a few optimizations to reduce the number of allocations.

### Modifications:

Added baseline performance and allocation tests for `scheduleTask` and `execute`
This commit is contained in:
Franz Busch 2021-12-13 17:33:13 +01:00 committed by GitHub
parent 094cd8c6e8
commit 213eb6887e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 251 additions and 19 deletions

View File

@ -16,20 +16,22 @@ import Dispatch
import NIOPosix
func run(identifier: String) {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let loop = group.next()
let dg = DispatchGroup()
measure(identifier: identifier) {
loop.execute {
for _ in 0..<10_000 {
dg.enter()
loop.scheduleTask(in: .nanoseconds(0)) { dg.leave() }
}
}
dg.wait()
return 10_000
}
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let loop = group.next()
let counter = try! loop.submit { () -> Int in
var counter: Int = 0
try! group.syncShutdownGracefully()
for _ in 0..<10000 {
loop.scheduleTask(in: .hours(1)) {
counter &+= 1
}
}
return counter
}.wait()
try! group.syncShutdownGracefully()
return counter
}
}

View File

@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Dispatch
import NIOPosix
func run(identifier: String) {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let loop = group.next()
let dg = DispatchGroup()
measure(identifier: identifier) {
var counter = 0
try! loop.submit {
for _ in 0..<10000 {
dg.enter()
loop.scheduleTask(in: .nanoseconds(0)) {
counter &+= 1
dg.leave()
}
}
}.wait()
dg.wait()
return counter
}
try! group.syncShutdownGracefully()
}

View File

@ -0,0 +1,60 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import NIOCore
import NIOPosix
final class ExecuteBenchmark: Benchmark {
private var group: MultiThreadedEventLoopGroup!
private var loop: EventLoop!
private var dg: DispatchGroup!
private var counter = 0
func setUp() throws {
group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
loop = group.next()
dg = DispatchGroup()
// We are preheating the EL to avoid growing the `ScheduledTask` `PriorityQueue`
// during the actual test
try! self.loop.submit {
var counter: Int = 0
for _ in 0..<100000 {
self.loop.scheduleTask(in: .nanoseconds(0)) {
counter &+= 1
}
}
}.wait()
}
func tearDown() { }
func run() -> Int {
try! self.loop.submit {
for _ in 0..<10000 {
self.dg.enter()
self.loop.execute {
self.counter &+= 1
self.dg.leave()
}
}
}.wait()
self.dg.wait()
return self.counter
}
}

View File

@ -0,0 +1,60 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import NIOCore
import NIOPosix
final class SchedulingAndRunningBenchmark: Benchmark {
private var group: MultiThreadedEventLoopGroup!
private var loop: EventLoop!
private var dg: DispatchGroup!
private var counter = 0
func setUp() throws {
group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
loop = group.next()
dg = DispatchGroup()
// We are preheating the EL to avoid growing the `ScheduledTask` `PriorityQueue`
// during the actual test
try! self.loop.submit {
var counter: Int = 0
for _ in 0..<100000 {
self.loop.scheduleTask(in: .nanoseconds(0)) {
counter &+= 1
}
}
}.wait()
}
func tearDown() { }
func run() -> Int {
try! self.loop.submit {
for _ in 0..<10000 {
self.dg.enter()
self.loop.scheduleTask(in: .nanoseconds(0)) {
self.counter &+= 1
self.dg.leave()
}
}
}.wait()
self.dg.wait()
return counter
}
}

View File

@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import NIOCore
import NIOPosix
final class SchedulingBenchmark: Benchmark {
private var group: MultiThreadedEventLoopGroup!
private var loop: EventLoop!
func setUp() throws {
group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
loop = group.next()
// We are preheating the EL to avoid growing the `ScheduledTask` `PriorityQueue`
// during the actual test
try! self.loop.submit {
var counter: Int = 0
for _ in 0..<100000 {
self.loop.scheduleTask(in: .nanoseconds(0)) {
counter &+= 1
}
}
}.wait()
}
func tearDown() { }
func run() -> Int {
let counter = try! self.loop.submit { () -> Int in
var counter: Int = 0
for _ in 0..<10000 {
self.loop.scheduleTask(in: .hours(1)) {
counter &+= 1
}
}
return counter
}.wait()
return counter
}
}

View File

@ -833,4 +833,11 @@ try measureAndPrint(desc: "lock_4_threads_10M_ops",
try measureAndPrint(desc: "lock_8_threads_10M_ops",
benchmark: LockBenchmark(numberOfThreads: 8, lockOperationsPerThread: 1_250_000))
try measureAndPrint(desc: "schedule_10000_tasks",
benchmark: SchedulingBenchmark())
try measureAndPrint(desc: "schedule_and_run_10000_tasks",
benchmark: SchedulingAndRunningBenchmark())
try measureAndPrint(desc: "execute_10000",
benchmark: ExecuteBenchmark())

View File

@ -51,7 +51,8 @@ services:
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=2050
- MAX_ALLOCS_ALLOWED_ping_pong_1000_reqs_1_conn=4400
- MAX_ALLOCS_ALLOWED_read_10000_chunks_from_file=190050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90150
- MAX_ALLOCS_ALLOWED_schedule_and_run_10000_tasks=100050
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=12200
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=188050

View File

@ -51,7 +51,8 @@ services:
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=2050
- MAX_ALLOCS_ALLOWED_ping_pong_1000_reqs_1_conn=4400
- MAX_ALLOCS_ALLOWED_read_10000_chunks_from_file=200050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90150
- MAX_ALLOCS_ALLOWED_schedule_and_run_10000_tasks=100050
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=12200
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=188050

View File

@ -51,7 +51,8 @@ services:
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=2050
- MAX_ALLOCS_ALLOWED_ping_pong_1000_reqs_1_conn=4400
- MAX_ALLOCS_ALLOWED_read_10000_chunks_from_file=190050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90150
- MAX_ALLOCS_ALLOWED_schedule_and_run_10000_tasks=100050
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=12200
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=190050

View File

@ -50,7 +50,8 @@ services:
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=2050
- MAX_ALLOCS_ALLOWED_ping_pong_1000_reqs_1_conn=4400
- MAX_ALLOCS_ALLOWED_read_10000_chunks_from_file=190050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90150
- MAX_ALLOCS_ALLOWED_schedule_and_run_10000_tasks=100050
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=12200
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=190050

View File

@ -51,7 +51,8 @@ services:
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=2050
- MAX_ALLOCS_ALLOWED_ping_pong_1000_reqs_1_conn=4400
- MAX_ALLOCS_ALLOWED_read_10000_chunks_from_file=160050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90150
- MAX_ALLOCS_ALLOWED_schedule_and_run_10000_tasks=100050
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=12200
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=188050