swift-nio/Sources/NIOPerformanceTester/main.swift

1148 lines
36 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
//
//===----------------------------------------------------------------------===//
import NIOCore
import NIOPosix
import NIOEmbedded
import NIOHTTP1
import NIOFoundationCompat
import Dispatch
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
var warning: String = ""
assert({
print("======================================================")
print("= YOU ARE RUNNING NIOPerformanceTester IN DEBUG MODE =")
print("======================================================")
warning = " <<< DEBUG MODE >>>"
return true
}())
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 -> Void {
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 -> Void {
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(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true).flatMap {
channel.pipeline.addHandler(SimpleHTTPServer())
}
}.bind(host: "127.0.0.1", port: 0).wait()
defer {
try! serverChannel.close().wait()
}
var head = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/perf-test-1")
head.headers.add(name: "Host", value: "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, eventLoop: EventLoop) {
self.remainingNumberOfRequests = numberOfRequests
self.numberOfRequests = numberOfRequests
self.isDonePromise = eventLoop.makePromise()
}
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().map { self.doneRequests }.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: "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.execute {
self.kickOff(channel: channel)
}
} else {
self.completionHandler(self.numberOfRequests)
}
}
func kickOff(channel: Channel) -> Void {
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
}
try channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true,
withErrorHandling: true).flatMap {
channel.pipeline.addHandler(SimpleHTTPServer())
}.flatMap {
channel.pipeline.addHandler(measuringHandler, position: .first)
}.wait()
measuringHandler.kickOff(channel: channel)
while !done {
eventLoop.run()
}
_ = try channel.finish()
precondition(requestsDone == desiredRequests)
return requestsDone
}
measureAndPrint(desc: "http1_1k_reqs_1_conn") {
let repeatedRequestsHandler = RepeatedRequests(numberOfRequests: 1_000, eventLoop: group.next())
let clientChannel = try! ClientBootstrap(group: group)
.channelInitializer { channel in
channel.pipeline.addHTTPClientHandlers().flatMap {
channel.pipeline.addHandler(repeatedRequestsHandler)
}
}
.connect(to: serverChannel.localAddress!)
.wait()
clientChannel.write(NIOAny(HTTPClientRequestPart.head(head)), promise: nil)
try! clientChannel.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil))).wait()
return try! repeatedRequestsHandler.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 repeatedRequestsHandler = RepeatedRequests(numberOfRequests: reqsPerConn, eventLoop: group.next())
let clientChannel = try! ClientBootstrap(group: group)
.channelInitializer { channel in
channel.pipeline.addHTTPClientHandlers().flatMap {
channel.pipeline.addHandler(repeatedRequestsHandler)
}
}
.connect(to: serverChannel.localAddress!)
.wait()
clientChannel.write(NIOAny(HTTPClientRequestPart.head(head)), promise: nil)
try! clientChannel.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil))).wait()
reqs.append(try! repeatedRequestsHandler.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, +).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: "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_100k_tasks",
benchmark: SchedulingBenchmark(numTasks: 100_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: "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)
)
}