mirror of
https://github.com/apple/swift-nio.git
synced 2026-05-20 20:30:36 +00:00
**TL;DR** This change leads to a ~90% reduction in observed system CPU time for some use cases by waking a single thread, instead of all idle threads. # Changes Inlining the commit messages here. ## Add NIOThreadPool submit throughput benchmarks ### Motivation `NIOThreadPool` had no benchmarks measuring submit overhead. This makes it difficult to evaluate the cost of signalling changes or to catch latency regressions. ### Modifications Add thread pool submit benchmarks, covering use cases with 4-thread and 16-thread pools. ### Result `NIOThreadPool` submit throughput and context-switch overhead are now tracked by benchmarks. ### Benchmark Results <details> ``` NIOThreadPool.serial_wakeup(16 threads) ╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ Context switches (K) │ 77 │ 78 │ 79 │ 80 │ 80 │ 92 │ 92 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Syscalls (total) (K) * │ 106 │ 107 │ 108 │ 109 │ 110 │ 116 │ 116 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (system CPU) (ms) * │ 1649 │ 1752 │ 1768 │ 1795 │ 1826 │ 1929 │ 1929 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (total CPU) (ms) * │ 1701 │ 1805 │ 1821 │ 1849 │ 1879 │ 1987 │ 1987 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (user CPU) (ms) * │ 51 │ 52 │ 52 │ 53 │ 53 │ 57 │ 57 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (wall clock) (ms) * │ 167 │ 177 │ 178 │ 181 │ 183 │ 200 │ 200 │ 30 │ ╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ NIOThreadPool.serial_wakeup(4 threads) ╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ Context switches (K) │ 44 │ 44 │ 44 │ 45 │ 45 │ 45 │ 45 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Syscalls (total) (K) * │ 65 │ 65 │ 65 │ 66 │ 66 │ 67 │ 67 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (system CPU) (ms) * │ 159 │ 162 │ 163 │ 165 │ 166 │ 169 │ 169 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (total CPU) (ms) * │ 178 │ 182 │ 183 │ 185 │ 186 │ 190 │ 190 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (user CPU) (ms) * │ 19 │ 19 │ 20 │ 20 │ 20 │ 21 │ 21 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (wall clock) (ms) * │ 76 │ 79 │ 79 │ 80 │ 80 │ 82 │ 82 │ 30 │ ╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ ``` </details> ## Replace `ConditionLock` with wake-one signalling `NIOThreadPoolWorkAvailable` ### Motivation `NIOThreadPool` used `ConditionLock` which calls `pthread_cond_broadcast` on every state change, waking all threads when only one work item is enqueued. This causes a thundering-herd problem. ### Modifications Add `NIOThreadPoolWorkAvailable` in NIOConcurrencyHelpers that uses `pthread_cond_signal` (wake-one) for work submission and `pthread_cond_broadcast` only for shutdown. Replace `ConditionLock<_WorkState>` and the `_WorkState` enum in `NIOThreadPool` with this new primitive. ### Result Submitting a work item wakes exactly **one** thread instead of all threads. ### Benchmark Results <details> ``` NIOThreadPool.serial_wakeup(16 threads) ╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ Context switches (K) │ 20 │ 20 │ 20 │ 20 │ 20 │ 20 │ 20 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Syscalls (total) (K) * │ 40 │ 40 │ 40 │ 40 │ 40 │ 40 │ 40 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (system CPU) (ms) * │ 47 │ 49 │ 49 │ 50 │ 50 │ 55 │ 55 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (total CPU) (ms) * │ 57 │ 58 │ 59 │ 60 │ 61 │ 67 │ 67 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (user CPU) (ms) * │ 10 │ 10 │ 10 │ 10 │ 10 │ 12 │ 12 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (wall clock) (ms) * │ 54 │ 55 │ 56 │ 56 │ 57 │ 65 │ 65 │ 30 │ ╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ NIOThreadPool.serial_wakeup(4 threads) ╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕ │ Metric │ p0 │ p25 │ p50 │ p75 │ p90 │ p99 │ p100 │ Samples │ ╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡ │ Context switches (K) │ 20 │ 20 │ 20 │ 20 │ 20 │ 20 │ 20 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Syscalls (total) (K) * │ 40 │ 40 │ 40 │ 40 │ 40 │ 40 │ 40 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (system CPU) (ms) * │ 45 │ 46 │ 46 │ 47 │ 57 │ 75 │ 75 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (total CPU) (ms) * │ 54 │ 55 │ 56 │ 57 │ 68 │ 87 │ 87 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (user CPU) (μs) * │ 9055 │ 9372 │ 9478 │ 9765 │ 10887 │ 12585 │ 12585 │ 30 │ ├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ │ Time (wall clock) (ms) * │ 52 │ 53 │ 53 │ 54 │ 64 │ 124 │ 124 │ 30 │ ╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛ ``` </details>
1252 lines
39 KiB
Swift
1252 lines
39 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// swift-format-ignore: AmbiguousTrailingClosureOverload
|
|
|
|
// swift-format-ignore: OrderedImports
|
|
// Required due to https://github.com/swiftlang/swift/issues/76842
|
|
|
|
#if canImport(Glibc)
|
|
@preconcurrency import Glibc
|
|
#elseif canImport(Musl)
|
|
@preconcurrency import Musl
|
|
#endif
|
|
|
|
import Dispatch
|
|
import NIOCore
|
|
import NIOEmbedded
|
|
import NIOFoundationCompat
|
|
import NIOHTTP1
|
|
#if os(Android)
|
|
// workaround for error: reference to var 'stdout' is not concurrency-safe because it involves shared mutable state
|
|
@preconcurrency import NIOPosix
|
|
#else
|
|
import NIOPosix
|
|
#endif
|
|
import NIOWebSocket
|
|
|
|
// Use unbuffered stdout to help detect exactly which test was running in the event of a crash.
|
|
setbuf(stdout, nil)
|
|
|
|
// MARK: Test Harness
|
|
|
|
func makeWarning() -> String {
|
|
var warning = ""
|
|
assert(
|
|
{
|
|
print("======================================================")
|
|
print("= YOU ARE RUNNING NIOPerformanceTester IN DEBUG MODE =")
|
|
print("======================================================")
|
|
warning = " <<< DEBUG MODE >>>"
|
|
return true
|
|
}()
|
|
)
|
|
return warning
|
|
}
|
|
|
|
let warning = makeWarning()
|
|
|
|
public func measure(_ fn: () throws -> Int) rethrows -> [Double] {
|
|
func measureOne(_ fn: () throws -> Int) rethrows -> Double {
|
|
let start = DispatchTime.now().uptimeNanoseconds
|
|
_ = try fn()
|
|
let end = DispatchTime.now().uptimeNanoseconds
|
|
return Double(end - start) / Double(TimeAmount.seconds(1).nanoseconds)
|
|
}
|
|
|
|
_ = try measureOne(fn) // pre-heat and throw away
|
|
var measurements = Array(repeating: 0.0, count: 10)
|
|
for i in 0..<10 {
|
|
measurements[i] = try measureOne(fn)
|
|
}
|
|
|
|
return measurements
|
|
}
|
|
|
|
let limitSet = CommandLine.arguments.dropFirst()
|
|
|
|
public func measureAndPrint(desc: String, fn: () throws -> Int) rethrows {
|
|
if limitSet.isEmpty || limitSet.contains(desc) {
|
|
print("measuring\(warning): \(desc): ", terminator: "")
|
|
let measurements = try measure(fn)
|
|
print(measurements.reduce(into: "") { $0.append("\($1), ") })
|
|
} else {
|
|
print("skipping '\(desc)', limit set = \(limitSet)")
|
|
}
|
|
}
|
|
|
|
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
|
public func measure(_ fn: () async throws -> Int) async rethrows -> [Double] {
|
|
func measureOne(_ fn: () async throws -> Int) async rethrows -> Double {
|
|
let start = DispatchTime.now().uptimeNanoseconds
|
|
_ = try await fn()
|
|
let end = DispatchTime.now().uptimeNanoseconds
|
|
return Double(end - start) / Double(TimeAmount.seconds(1).nanoseconds)
|
|
}
|
|
|
|
_ = try await measureOne(fn) // pre-heat and throw away
|
|
var measurements = Array(repeating: 0.0, count: 10)
|
|
for i in 0..<10 {
|
|
measurements[i] = try await measureOne(fn)
|
|
}
|
|
|
|
return measurements
|
|
}
|
|
|
|
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
|
public func measureAndPrint(desc: String, fn: () async throws -> Int) async rethrows {
|
|
if limitSet.isEmpty || limitSet.contains(desc) {
|
|
print("measuring\(warning): \(desc): ", terminator: "")
|
|
let measurements = try await measure(fn)
|
|
print(measurements.reduce(into: "") { $0.append("\($1), ") })
|
|
} else {
|
|
print("skipping '\(desc)', limit set = \(limitSet)")
|
|
}
|
|
}
|
|
|
|
// MARK: Utilities
|
|
|
|
private final class SimpleHTTPServer: ChannelInboundHandler {
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
typealias OutboundOut = HTTPServerResponsePart
|
|
|
|
private var files: [String] = Array()
|
|
private var seenEnd: Bool = false
|
|
private var sentEnd: Bool = false
|
|
private var isOpen: Bool = true
|
|
|
|
private let cachedHead: HTTPResponseHead
|
|
private let cachedBody: [UInt8]
|
|
private let bodyLength = 1024
|
|
private let numberOfAdditionalHeaders = 10
|
|
|
|
init() {
|
|
var head = HTTPResponseHead(version: .http1_1, status: .ok)
|
|
head.headers.add(name: "Content-Length", value: "\(self.bodyLength)")
|
|
for i in 0..<self.numberOfAdditionalHeaders {
|
|
head.headers.add(name: "X-Random-Extra-Header", value: "\(i)")
|
|
}
|
|
self.cachedHead = head
|
|
|
|
var body: [UInt8] = []
|
|
body.reserveCapacity(self.bodyLength)
|
|
for i in 0..<self.bodyLength {
|
|
body.append(UInt8(i % Int(UInt8.max)))
|
|
}
|
|
self.cachedBody = body
|
|
}
|
|
|
|
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
if case .head(let req) = Self.unwrapInboundIn(data) {
|
|
switch req.uri {
|
|
case "/perf-test-1":
|
|
var buffer = context.channel.allocator.buffer(capacity: self.cachedBody.count)
|
|
buffer.writeBytes(self.cachedBody)
|
|
context.write(Self.wrapOutboundOut(.head(self.cachedHead)), promise: nil)
|
|
context.write(Self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
|
|
context.writeAndFlush(Self.wrapOutboundOut(.end(nil)), promise: nil)
|
|
return
|
|
case "/perf-test-2":
|
|
var req = HTTPResponseHead(version: .http1_1, status: .ok)
|
|
for i in 1...8 {
|
|
req.headers.add(name: "X-ResponseHeader-\(i)", value: "foo")
|
|
}
|
|
req.headers.add(name: "content-length", value: "0")
|
|
context.write(Self.wrapOutboundOut(.head(req)), promise: nil)
|
|
context.writeAndFlush(Self.wrapOutboundOut(.end(nil)), promise: nil)
|
|
return
|
|
default:
|
|
fatalError("unknown uri \(req.uri)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
|
defer {
|
|
try! group.syncShutdownGracefully()
|
|
}
|
|
|
|
let serverChannel = try ServerBootstrap(group: group)
|
|
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.eventLoop.makeCompletedFuture {
|
|
try channel.pipeline.syncOperations.configureHTTPServerPipeline(withPipeliningAssistance: true)
|
|
try channel.pipeline.syncOperations.addHandler(SimpleHTTPServer())
|
|
}
|
|
}.bind(host: "127.0.0.1", port: 0).wait()
|
|
|
|
defer {
|
|
try! serverChannel.close().wait()
|
|
}
|
|
|
|
let head = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/perf-test-1", headers: ["Host": "localhost"])
|
|
|
|
final class RepeatedRequests: ChannelInboundHandler {
|
|
typealias InboundIn = HTTPClientResponsePart
|
|
typealias OutboundOut = HTTPClientRequestPart
|
|
|
|
private let numberOfRequests: Int
|
|
private var remainingNumberOfRequests: Int
|
|
private var doneRequests = 0
|
|
private let isDonePromise: EventLoopPromise<Int>
|
|
|
|
init(numberOfRequests: Int, isDonePromise: EventLoopPromise<Int>) {
|
|
self.remainingNumberOfRequests = numberOfRequests
|
|
self.numberOfRequests = numberOfRequests
|
|
self.isDonePromise = isDonePromise
|
|
}
|
|
|
|
func wait() throws -> Int {
|
|
let reqs = try self.isDonePromise.futureResult.wait()
|
|
precondition(reqs == self.numberOfRequests)
|
|
return reqs
|
|
}
|
|
|
|
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
|
context.channel.close(promise: nil)
|
|
self.isDonePromise.fail(error)
|
|
}
|
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
let reqPart = Self.unwrapInboundIn(data)
|
|
if case .end(nil) = reqPart {
|
|
if self.remainingNumberOfRequests <= 0 {
|
|
context.channel.close().assumeIsolated().map {
|
|
self.doneRequests
|
|
}.nonisolated().cascade(to: self.isDonePromise)
|
|
} else {
|
|
self.doneRequests += 1
|
|
self.remainingNumberOfRequests -= 1
|
|
|
|
context.write(Self.wrapOutboundOut(.head(head)), promise: nil)
|
|
context.writeAndFlush(Self.wrapOutboundOut(.end(nil)), promise: nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func someString(size: Int) -> String {
|
|
var s = "A"
|
|
for f in 1..<size {
|
|
s += String("\(f)".first!)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// MARK: Performance Tests
|
|
|
|
measureAndPrint(desc: "write_http_headers") {
|
|
var headers: [(String, String)] = []
|
|
for i in 1..<10 {
|
|
headers.append(("\(i)", "\(i)"))
|
|
}
|
|
|
|
var val = 0
|
|
for _ in 0..<1_000_000 {
|
|
let headers = HTTPHeaders(headers)
|
|
val += headers.underestimatedCount
|
|
}
|
|
return val
|
|
}
|
|
|
|
measureAndPrint(desc: "http_headers_canonical_form") {
|
|
let headers: HTTPHeaders = ["key": "no,trimming"]
|
|
var count = 0
|
|
for _ in 0..<100_000 {
|
|
count &+= headers[canonicalForm: "key"].count
|
|
}
|
|
return count
|
|
}
|
|
|
|
measureAndPrint(desc: "http_headers_canonical_form_trimming_whitespace") {
|
|
let headers: HTTPHeaders = ["key": " some , trimming "]
|
|
var count = 0
|
|
for _ in 0..<10_000 {
|
|
count &+= headers[canonicalForm: "key"].count
|
|
}
|
|
return count
|
|
}
|
|
|
|
measureAndPrint(desc: "http_headers_canonical_form_trimming_whitespace_from_short_string") {
|
|
let headers: HTTPHeaders = ["key": " smallString ,whenStripped"]
|
|
var count = 0
|
|
for _ in 0..<10_000 {
|
|
count &+= headers[canonicalForm: "key"].count
|
|
}
|
|
return count
|
|
}
|
|
|
|
measureAndPrint(desc: "http_headers_canonical_form_trimming_whitespace_from_long_string") {
|
|
let headers: HTTPHeaders = ["key": " moreThan15CharactersWithAndWithoutWhitespace ,anotherValue"]
|
|
var count = 0
|
|
for _ in 0..<10_000 {
|
|
count &+= headers[canonicalForm: "key"].count
|
|
}
|
|
return count
|
|
}
|
|
|
|
measureAndPrint(desc: "http_headers_description_100k") {
|
|
let headers = HTTPHeaders(Array(repeating: ("String", "String"), count: 100))
|
|
|
|
for _ in 0..<100_000 {
|
|
let str = headers.description
|
|
precondition(str.utf8.count > 100)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_12MB_short_string_literals") {
|
|
let bufferSize = 12 * 1024 * 1024
|
|
var buffer = ByteBufferAllocator().buffer(capacity: bufferSize)
|
|
|
|
for _ in 0..<3 {
|
|
buffer.clear()
|
|
for _ in 0..<(bufferSize / 4) {
|
|
buffer.writeString("abcd")
|
|
}
|
|
}
|
|
|
|
let readableBytes = buffer.readableBytes
|
|
precondition(readableBytes == bufferSize)
|
|
return readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_12MB_short_calculated_strings") {
|
|
let bufferSize = 12 * 1024 * 1024
|
|
var buffer = ByteBufferAllocator().buffer(capacity: bufferSize)
|
|
let s = someString(size: 4)
|
|
|
|
for _ in 0..<1 {
|
|
buffer.clear()
|
|
for _ in 0..<(bufferSize / 4) {
|
|
buffer.writeString(s)
|
|
}
|
|
}
|
|
|
|
let readableBytes = buffer.readableBytes
|
|
precondition(readableBytes == bufferSize)
|
|
return readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_12MB_medium_string_literals") {
|
|
let bufferSize = 12 * 1024 * 1024
|
|
var buffer = ByteBufferAllocator().buffer(capacity: bufferSize)
|
|
|
|
for _ in 0..<100 {
|
|
buffer.clear()
|
|
for _ in 0..<(bufferSize / 24) {
|
|
buffer.writeString("012345678901234567890123")
|
|
}
|
|
}
|
|
|
|
let readableBytes = buffer.readableBytes
|
|
precondition(readableBytes == bufferSize)
|
|
return readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_12MB_medium_calculated_strings") {
|
|
let bufferSize = 12 * 1024 * 1024
|
|
var buffer = ByteBufferAllocator().buffer(capacity: bufferSize)
|
|
let s = someString(size: 24)
|
|
|
|
for _ in 0..<5 {
|
|
buffer.clear()
|
|
for _ in 0..<(bufferSize / 24) {
|
|
buffer.writeString(s)
|
|
}
|
|
}
|
|
|
|
let readableBytes = buffer.readableBytes
|
|
precondition(readableBytes == bufferSize)
|
|
return readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_12MB_large_calculated_strings") {
|
|
let bufferSize = 12 * 1024 * 1024
|
|
var buffer = ByteBufferAllocator().buffer(capacity: bufferSize)
|
|
let s = someString(size: 1024 * 1024)
|
|
|
|
for _ in 0..<5 {
|
|
buffer.clear()
|
|
for _ in 0..<12 {
|
|
buffer.writeString(s)
|
|
}
|
|
}
|
|
|
|
let readableBytes = buffer.readableBytes
|
|
precondition(readableBytes == bufferSize)
|
|
return readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_lots_of_rw") {
|
|
let dispatchData = ("A" as StaticString).withUTF8Buffer { ptr in
|
|
DispatchData(bytes: UnsafeRawBufferPointer(start: UnsafeRawPointer(ptr.baseAddress), count: ptr.count))
|
|
}
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 7 * 1024 * 1024)
|
|
let substring = Substring("A")
|
|
@inline(never)
|
|
func doWrites(buffer: inout ByteBuffer, dispatchData: DispatchData, substring: Substring) {
|
|
// all of those should be 0 allocations
|
|
|
|
// buffer.writeBytes(foundationData) // see SR-7542
|
|
buffer.writeBytes([0x41])
|
|
buffer.writeBytes(dispatchData)
|
|
buffer.writeBytes("A".utf8)
|
|
buffer.writeString("A")
|
|
buffer.writeStaticString("A")
|
|
buffer.writeInteger(0x41, as: UInt8.self)
|
|
buffer.writeSubstring(substring)
|
|
}
|
|
@inline(never)
|
|
func doReads(buffer: inout ByteBuffer) {
|
|
// these ones are zero allocations
|
|
let val = buffer.readInteger(as: UInt8.self)
|
|
precondition(0x41 == val, "\(val!)")
|
|
var slice = buffer.readSlice(length: 1)
|
|
let sliceVal = slice!.readInteger(as: UInt8.self)
|
|
precondition(0x41 == sliceVal, "\(sliceVal!)")
|
|
buffer.withUnsafeReadableBytes { ptr in
|
|
precondition(ptr[0] == 0x41)
|
|
}
|
|
|
|
// those down here should be one allocation each
|
|
let arr = buffer.readBytes(length: 1)
|
|
precondition([0x41] == arr!, "\(arr!)")
|
|
let str = buffer.readString(length: 1)
|
|
precondition("A" == str, "\(str!)")
|
|
}
|
|
for _ in 0..<100_000 {
|
|
doWrites(buffer: &buffer, dispatchData: dispatchData, substring: substring)
|
|
doReads(buffer: &buffer)
|
|
}
|
|
return buffer.readableBytes
|
|
}
|
|
|
|
func writeExampleHTTPResponseAsString(buffer: inout ByteBuffer) {
|
|
buffer.writeString("HTTP/1.1 200 OK")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Connection")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("close")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Proxy-Connection")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("close")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Via")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("HTTP/1.1 localhost (IBM-PROXY-WTE)")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Date")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("Tue, 08 May 2018 13:42:56 GMT")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Server")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("Apache/2.2.15 (Red Hat)")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Strict-Transport-Security")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("max-age=15768000; includeSubDomains")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Last-Modified")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("Tue, 08 May 2018 13:39:13 GMT")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("ETag")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("357031-1809-56bb1e96a6240")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Accept-Ranges")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("bytes")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Content-Length")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("6153")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("Content-Type")
|
|
buffer.writeString(":")
|
|
buffer.writeString(" ")
|
|
buffer.writeString("text/html; charset=UTF-8")
|
|
buffer.writeString("\r\n")
|
|
buffer.writeString("\r\n")
|
|
}
|
|
|
|
func writeExampleHTTPResponseAsStaticString(buffer: inout ByteBuffer) {
|
|
buffer.writeStaticString("HTTP/1.1 200 OK")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Connection")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("close")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Proxy-Connection")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("close")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Via")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("HTTP/1.1 localhost (IBM-PROXY-WTE)")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Date")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("Tue, 08 May 2018 13:42:56 GMT")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Server")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("Apache/2.2.15 (Red Hat)")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Strict-Transport-Security")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("max-age=15768000; includeSubDomains")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Last-Modified")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("Tue, 08 May 2018 13:39:13 GMT")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("ETag")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("357031-1809-56bb1e96a6240")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Accept-Ranges")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("bytes")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Content-Length")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("6153")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("Content-Type")
|
|
buffer.writeStaticString(":")
|
|
buffer.writeStaticString(" ")
|
|
buffer.writeStaticString("text/html; charset=UTF-8")
|
|
buffer.writeStaticString("\r\n")
|
|
buffer.writeStaticString("\r\n")
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_http_response_ascii_only_as_string") {
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 16 * 1024)
|
|
for _ in 0..<20_000 {
|
|
writeExampleHTTPResponseAsString(buffer: &buffer)
|
|
buffer.writeString(htmlASCIIOnly)
|
|
buffer.clear()
|
|
}
|
|
return buffer.readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_http_response_ascii_only_as_staticstring") {
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 16 * 1024)
|
|
for _ in 0..<20_000 {
|
|
writeExampleHTTPResponseAsStaticString(buffer: &buffer)
|
|
buffer.writeStaticString(htmlASCIIOnlyStaticString)
|
|
buffer.clear()
|
|
}
|
|
return buffer.readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_http_response_some_nonascii_as_string") {
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 16 * 1024)
|
|
for _ in 0..<20_000 {
|
|
writeExampleHTTPResponseAsString(buffer: &buffer)
|
|
buffer.writeString(htmlMostlyASCII)
|
|
buffer.clear()
|
|
}
|
|
return buffer.readableBytes
|
|
}
|
|
|
|
measureAndPrint(desc: "bytebuffer_write_http_response_some_nonascii_as_staticstring") {
|
|
var buffer = ByteBufferAllocator().buffer(capacity: 16 * 1024)
|
|
for _ in 0..<20_000 {
|
|
writeExampleHTTPResponseAsStaticString(buffer: &buffer)
|
|
buffer.writeStaticString(htmlMostlyASCIIStaticString)
|
|
buffer.clear()
|
|
}
|
|
return buffer.readableBytes
|
|
}
|
|
|
|
try measureAndPrint(desc: "no-net_http1_1k_reqs_1_conn") {
|
|
final class MeasuringHandler: ChannelDuplexHandler {
|
|
typealias InboundIn = Never
|
|
typealias InboundOut = ByteBuffer
|
|
typealias OutboundIn = ByteBuffer
|
|
|
|
private var requestBuffer: ByteBuffer!
|
|
private var expectedResponseBuffer: ByteBuffer?
|
|
private var remainingNumberOfRequests: Int
|
|
|
|
private let completionHandler: (Int) -> Void
|
|
private let numberOfRequests: Int
|
|
|
|
init(numberOfRequests: Int, completionHandler: @escaping (Int) -> Void) {
|
|
self.completionHandler = completionHandler
|
|
self.numberOfRequests = numberOfRequests
|
|
self.remainingNumberOfRequests = numberOfRequests
|
|
}
|
|
|
|
func handlerAdded(context: ChannelHandlerContext) {
|
|
self.requestBuffer = context.channel.allocator.buffer(capacity: 512)
|
|
self.requestBuffer.writeString(
|
|
"""
|
|
GET /perf-test-2 HTTP/1.1\r
|
|
Host: example.com\r
|
|
X-Some-Header-1: foo\r
|
|
X-Some-Header-2: foo\r
|
|
X-Some-Header-3: foo\r
|
|
X-Some-Header-4: foo\r
|
|
X-Some-Header-5: foo\r
|
|
X-Some-Header-6: foo\r
|
|
X-Some-Header-7: foo\r
|
|
X-Some-Header-8: foo\r\n\r\n
|
|
"""
|
|
)
|
|
}
|
|
|
|
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
|
|
var buf = Self.unwrapOutboundIn(data)
|
|
if self.expectedResponseBuffer == nil {
|
|
self.expectedResponseBuffer = buf
|
|
}
|
|
precondition(buf == self.expectedResponseBuffer, "got \(buf.readString(length: buf.readableBytes)!)")
|
|
let channel = context.channel
|
|
self.remainingNumberOfRequests -= 1
|
|
if self.remainingNumberOfRequests > 0 {
|
|
context.eventLoop.assumeIsolated().execute {
|
|
self.kickOff(channel: channel)
|
|
}
|
|
} else {
|
|
self.completionHandler(self.numberOfRequests)
|
|
}
|
|
}
|
|
|
|
func kickOff(channel: Channel) {
|
|
try! (channel as! EmbeddedChannel).writeInbound(self.requestBuffer)
|
|
}
|
|
}
|
|
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let channel = EmbeddedChannel(handler: nil, loop: eventLoop)
|
|
var done = false
|
|
let desiredRequests = 1_000
|
|
var requestsDone = -1
|
|
let measuringHandler = MeasuringHandler(numberOfRequests: desiredRequests) { reqs in
|
|
requestsDone = reqs
|
|
done = true
|
|
}
|
|
|
|
let sync = channel.pipeline.syncOperations
|
|
try sync.configureHTTPServerPipeline(
|
|
withPipeliningAssistance: true,
|
|
withErrorHandling: true
|
|
)
|
|
|
|
try sync.addHandler(SimpleHTTPServer())
|
|
try sync.addHandler(measuringHandler, position: .first)
|
|
|
|
measuringHandler.kickOff(channel: channel)
|
|
|
|
while !done {
|
|
eventLoop.run()
|
|
}
|
|
_ = try channel.finish()
|
|
precondition(requestsDone == desiredRequests)
|
|
return requestsDone
|
|
}
|
|
|
|
measureAndPrint(desc: "http1_1k_reqs_1_conn") {
|
|
let isDone = group.next().makePromise(of: Int.self)
|
|
let clientChannel = try! ClientBootstrap(group: group)
|
|
.channelInitializer { channel in
|
|
channel.eventLoop.makeCompletedFuture {
|
|
let repeatedRequestsHandler = RepeatedRequests(numberOfRequests: 1_000, isDonePromise: isDone)
|
|
let sync = channel.pipeline.syncOperations
|
|
try sync.addHTTPClientHandlers()
|
|
try sync.addHandler(repeatedRequestsHandler)
|
|
}
|
|
}
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait()
|
|
|
|
try! clientChannel.eventLoop.flatSubmit {
|
|
let promise = clientChannel.eventLoop.makePromise(of: Void.self)
|
|
clientChannel.pipeline.syncOperations.write(NIOAny(HTTPClientRequestPart.head(head)), promise: nil)
|
|
clientChannel.pipeline.syncOperations.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil)), promise: promise)
|
|
return promise.futureResult
|
|
}.wait()
|
|
return try! isDone.futureResult.wait()
|
|
}
|
|
|
|
measureAndPrint(desc: "http1_1k_reqs_100_conns") {
|
|
var reqs: [Int] = []
|
|
let numConns = 100
|
|
let numReqs = 1_000
|
|
let reqsPerConn = numReqs / numConns
|
|
reqs.reserveCapacity(reqsPerConn)
|
|
for _ in 0..<numConns {
|
|
let isDone = group.next().makePromise(of: Int.self)
|
|
|
|
let clientChannel = try! ClientBootstrap(group: group)
|
|
.channelInitializer { channel in
|
|
channel.eventLoop.makeCompletedFuture {
|
|
let repeatedRequestsHandler = RepeatedRequests(numberOfRequests: reqsPerConn, isDonePromise: isDone)
|
|
let sync = channel.pipeline.syncOperations
|
|
try sync.addHTTPClientHandlers()
|
|
try sync.addHandler(repeatedRequestsHandler)
|
|
}
|
|
}
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait()
|
|
|
|
try! clientChannel.eventLoop.flatSubmit {
|
|
let promise = clientChannel.eventLoop.makePromise(of: Void.self)
|
|
clientChannel.pipeline.syncOperations.write(NIOAny(HTTPClientRequestPart.head(head)), promise: nil)
|
|
clientChannel.pipeline.syncOperations.writeAndFlush(
|
|
NIOAny(HTTPClientRequestPart.end(nil)),
|
|
promise: promise
|
|
)
|
|
return promise.futureResult
|
|
}.wait()
|
|
reqs.append(try! isDone.futureResult.wait())
|
|
}
|
|
return reqs.reduce(0, +) / numConns
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallsucceed_100k_immediately_succeeded_off_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<100_000)
|
|
let futures = expected.map { loop.makeSucceededFuture($0) }
|
|
let allSucceeded = try! EventLoopFuture.whenAllSucceed(futures, on: loop).wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallsucceed_100k_immediately_succeeded_on_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<100_000)
|
|
let allSucceeded = try! loop.makeSucceededFuture(()).flatMap { _ -> EventLoopFuture<[Int]> in
|
|
let futures = expected.map { loop.makeSucceededFuture($0) }
|
|
return EventLoopFuture.whenAllSucceed(futures, on: loop)
|
|
}.wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallsucceed_10k_deferred_off_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<10_000)
|
|
let promises = expected.map { _ in loop.makePromise(of: Int.self) }
|
|
let allSucceeded = EventLoopFuture.whenAllSucceed(promises.map { $0.futureResult }, on: loop)
|
|
for (index, promise) in promises.enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
return try! allSucceeded.wait().count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallsucceed_10k_deferred_on_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<10_000)
|
|
let promises = expected.map { _ in loop.makePromise(of: Int.self) }
|
|
let allSucceeded = try! loop.makeSucceededFuture(()).flatMap { _ -> EventLoopFuture<[Int]> in
|
|
let result = EventLoopFuture.whenAllSucceed(promises.map { $0.futureResult }, on: loop)
|
|
for (index, promise) in promises.enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
return result
|
|
}.wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallcomplete_100k_immediately_succeeded_off_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<100_000)
|
|
let futures = expected.map { loop.makeSucceededFuture($0) }
|
|
let allSucceeded = try! EventLoopFuture.whenAllComplete(futures, on: loop).wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallcomplete_100k_immediately_succeeded_on_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<100_000)
|
|
let allSucceeded = try! loop.makeSucceededFuture(()).flatMap { _ -> EventLoopFuture<[Result<Int, Error>]> in
|
|
let futures = expected.map { loop.makeSucceededFuture($0) }
|
|
return EventLoopFuture.whenAllComplete(futures, on: loop)
|
|
}.wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallcomplete_10k_deferred_off_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<10_000)
|
|
let promises = expected.map { _ in loop.makePromise(of: Int.self) }
|
|
let allSucceeded = EventLoopFuture.whenAllComplete(promises.map { $0.futureResult }, on: loop)
|
|
for (index, promise) in promises.enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
return try! allSucceeded.wait().count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_whenallcomplete_100k_deferred_on_loop") {
|
|
let loop = group.next()
|
|
let expected = Array(0..<100_000)
|
|
let promises = expected.map { _ in loop.makePromise(of: Int.self) }
|
|
let allSucceeded = try! loop.makeSucceededFuture(()).flatMap { _ -> EventLoopFuture<[Result<Int, Error>]> in
|
|
let result = EventLoopFuture.whenAllComplete(promises.map { $0.futureResult }, on: loop)
|
|
for (index, promise) in promises.enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
return result
|
|
}.wait()
|
|
return allSucceeded.count
|
|
}
|
|
|
|
measureAndPrint(desc: "future_reduce_10k_futures") {
|
|
let el1 = group.next()
|
|
|
|
let futures = (1...10_000).map { i in el1.makeSucceededFuture(i) }
|
|
return try! EventLoopFuture<Int>.reduce(0, futures, on: el1, { $0 + $1 }).wait()
|
|
}
|
|
|
|
measureAndPrint(desc: "future_reduce_into_10k_futures") {
|
|
let el1 = group.next()
|
|
|
|
let futures = (1...10_000).map { i in el1.makeSucceededFuture(i) }
|
|
return try! EventLoopFuture<Int>.reduce(into: 0, futures, on: el1, { $0 += $1 }).wait()
|
|
}
|
|
|
|
try measureAndPrint(desc: "el_in_eventloop_100M") {
|
|
let el1 = group.next()
|
|
|
|
let inEL = try el1.submit {
|
|
var inEL = 0
|
|
for _ in 0..<100_000_000 {
|
|
inEL = inEL &+ (el1.inEventLoop ? 1 : 0)
|
|
}
|
|
return inEL
|
|
}.wait()
|
|
precondition(inEL == 100_000_000)
|
|
return inEL
|
|
}
|
|
|
|
measureAndPrint(desc: "el_not_in_eventloop_100M") {
|
|
let el1 = group.next()
|
|
|
|
var inEL = 0
|
|
for _ in 0..<100_000_000 {
|
|
inEL = inEL &+ (el1.inEventLoop ? 1 : 0)
|
|
}
|
|
precondition(inEL == 0)
|
|
return inEL
|
|
}
|
|
|
|
try measureAndPrint(desc: "channel_pipeline_1m_events", benchmark: ChannelPipelineBenchmark(runCount: 1_000_000))
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_space_at_front_100k_frames_cow",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 100_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .always,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_space_at_front_1m_frames_cow_masking",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 1_000_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .always,
|
|
maskingKeyStrategy: .always
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_1kb_space_at_front_1m_frames_cow",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 1024,
|
|
runCount: 1_000_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .always,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_no_space_at_front_100k_frames_cow",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 100_000,
|
|
dataStrategy: .noSpaceAtFront,
|
|
cowStrategy: .always,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_1kb_no_space_at_front_100k_frames_cow",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 1024,
|
|
runCount: 100_000,
|
|
dataStrategy: .noSpaceAtFront,
|
|
cowStrategy: .always,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_space_at_front_100k_frames",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 100_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .never,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_space_at_front_10k_frames_masking",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 10_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .never,
|
|
maskingKeyStrategy: .always
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_1kb_space_at_front_10k_frames",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 1024,
|
|
runCount: 10_000,
|
|
dataStrategy: .spaceAtFront,
|
|
cowStrategy: .never,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_50b_no_space_at_front_100k_frames",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 50,
|
|
runCount: 100_000,
|
|
dataStrategy: .noSpaceAtFront,
|
|
cowStrategy: .never,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_encode_1kb_no_space_at_front_10k_frames",
|
|
benchmark: WebSocketFrameEncoderBenchmark(
|
|
dataSize: 1024,
|
|
runCount: 10_000,
|
|
dataStrategy: .noSpaceAtFront,
|
|
cowStrategy: .never,
|
|
maskingKeyStrategy: .never
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_125b_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: 125,
|
|
runCount: 10_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_125b_with_a_masking_key_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: 125,
|
|
runCount: 10_000,
|
|
maskingKey: [0x80, 0x08, 0x10, 0x01]
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_64kb_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: Int(UInt16.max),
|
|
runCount: 10_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_64kb_with_a_masking_key_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: Int(UInt16.max),
|
|
runCount: 10_000,
|
|
maskingKey: [0x80, 0x08, 0x10, 0x01]
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_64kb_+1_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: Int(UInt16.max) + 1,
|
|
runCount: 10_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "websocket_decode_64kb_+1_with_a_masking_key_10k_frames",
|
|
benchmark: WebSocketFrameDecoderBenchmark(
|
|
dataSize: Int(UInt16.max) + 1,
|
|
runCount: 10_000,
|
|
maskingKey: [0x80, 0x08, 0x10, 0x01]
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "circular_buffer_into_byte_buffer_1kb",
|
|
benchmark: CircularBufferIntoByteBufferBenchmark(
|
|
iterations: 10_000,
|
|
bufferSize: 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "circular_buffer_into_byte_buffer_1mb",
|
|
benchmark: CircularBufferIntoByteBufferBenchmark(
|
|
iterations: 20,
|
|
bufferSize: 1024 * 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "byte_buffer_view_iterator_1mb",
|
|
benchmark: ByteBufferViewIteratorBenchmark(
|
|
iterations: 20,
|
|
bufferSize: 1024 * 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "byte_buffer_view_contains_12mb",
|
|
benchmark: ByteBufferViewContainsBenchmark(
|
|
iterations: 5,
|
|
bufferSize: 12 * 1024 * 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "byte_to_message_decoder_decode_many_small",
|
|
benchmark: ByteToMessageDecoderDecodeManySmallsBenchmark(
|
|
iterations: 200,
|
|
bufferSize: 16384
|
|
)
|
|
)
|
|
|
|
measureAndPrint(desc: "generate_10k_random_request_keys") {
|
|
let numKeys = 10_000
|
|
return (0..<numKeys).reduce(
|
|
into: 0,
|
|
{ result, _ in
|
|
result &+= NIOWebSocketClientUpgrader.randomRequestKey().count
|
|
}
|
|
)
|
|
}
|
|
|
|
try measureAndPrint(
|
|
desc: "bytebuffer_rw_10_uint32s",
|
|
benchmark: ByteBufferReadWriteMultipleIntegersBenchmark<UInt32>(
|
|
iterations: 100_000,
|
|
numberOfInts: 10
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "bytebuffer_multi_rw_10_uint32s",
|
|
benchmark: ByteBufferMultiReadWriteTenIntegersBenchmark<UInt32>(
|
|
iterations: 1_000_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "lock_1_thread_10M_ops",
|
|
benchmark: NIOLockBenchmark(
|
|
numberOfThreads: 1,
|
|
lockOperationsPerThread: 10_000_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "lock_2_threads_10M_ops",
|
|
benchmark: NIOLockBenchmark(
|
|
numberOfThreads: 2,
|
|
lockOperationsPerThread: 5_000_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "lock_4_threads_10M_ops",
|
|
benchmark: NIOLockBenchmark(
|
|
numberOfThreads: 4,
|
|
lockOperationsPerThread: 2_500_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "lock_8_threads_10M_ops",
|
|
benchmark: NIOLockBenchmark(
|
|
numberOfThreads: 8,
|
|
lockOperationsPerThread: 1_250_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "schedule_and_run_100k_tasks",
|
|
benchmark: SchedulingAndRunningBenchmark(numTasks: 100_000)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "execute_100k_tasks",
|
|
benchmark: ExecuteBenchmark(numTasks: 100_000)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "runIfActive_1_thread_100k_tasks",
|
|
benchmark: RunIfActiveBenchmark(numThreads: 1, numTasks: 100_000)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "runIfActive_8_threads_100k_tasks",
|
|
benchmark: RunIfActiveBenchmark(numThreads: 8, numTasks: 100_000)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "bytebufferview_copy_to_array_100k_times_1kb",
|
|
benchmark: ByteBufferViewCopyToArrayBenchmark(
|
|
iterations: 100_000,
|
|
size: 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "circularbuffer_copy_to_array_10k_times_1kb",
|
|
benchmark: CircularBufferViewCopyToArrayBenchmark(
|
|
iterations: 10_000,
|
|
size: 1024
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "deadline_now_1M_times",
|
|
benchmark: DeadlineNowBenchmark(
|
|
iterations: 1_000_000
|
|
)
|
|
)
|
|
|
|
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
|
|
try measureAndPrint(
|
|
desc: "asyncwriter_single_writes_1M_times",
|
|
benchmark: NIOAsyncWriterSingleWritesBenchmark(
|
|
iterations: 1_000_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "asyncsequenceproducer_consume_1M_times",
|
|
benchmark: NIOAsyncSequenceProducerBenchmark(
|
|
iterations: 1_000_000
|
|
)
|
|
)
|
|
}
|
|
|
|
try measureAndPrint(
|
|
desc: "udp_10k_writes",
|
|
benchmark: UDPBenchmark(
|
|
data: ByteBuffer(repeating: 42, count: 1000),
|
|
numberOfRequests: 10_000,
|
|
vectorReads: 1,
|
|
vectorWrites: 1
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "udp_10k_vector_writes",
|
|
benchmark: UDPBenchmark(
|
|
data: ByteBuffer(repeating: 42, count: 1000),
|
|
numberOfRequests: 10_000,
|
|
vectorReads: 1,
|
|
vectorWrites: 10
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "udp_10k_vector_reads",
|
|
benchmark: UDPBenchmark(
|
|
data: ByteBuffer(repeating: 42, count: 1000),
|
|
numberOfRequests: 10_000,
|
|
vectorReads: 10,
|
|
vectorWrites: 1
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "udp_10k_vector_reads_and_writes",
|
|
benchmark: UDPBenchmark(
|
|
data: ByteBuffer(repeating: 42, count: 1000),
|
|
numberOfRequests: 10_000,
|
|
vectorReads: 10,
|
|
vectorWrites: 10
|
|
)
|
|
)
|
|
|
|
if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) {
|
|
try measureAndPrint(
|
|
desc: "tcp_100k_messages_throughput",
|
|
benchmark: TCPThroughputBenchmark(messages: 100_000, messageSize: 500)
|
|
)
|
|
}
|
|
|
|
try measureAndPrint(
|
|
desc: "thread_pool_serial_wakeup_4_threads_10k",
|
|
benchmark: NIOThreadPoolSerialWakeupBenchmark(
|
|
numberOfThreads: 4,
|
|
numberOfTasks: 10_000
|
|
)
|
|
)
|
|
|
|
try measureAndPrint(
|
|
desc: "thread_pool_serial_wakeup_16_threads_10k",
|
|
benchmark: NIOThreadPoolSerialWakeupBenchmark(
|
|
numberOfThreads: 16,
|
|
numberOfTasks: 10_000
|
|
)
|
|
)
|