Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a serialized variant of the Trie-router #402

Merged
merged 7 commits into from May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Benchmarks/Benchmarks/HTTP1/HTTP1ChannelBenchmarks.swift
Expand Up @@ -104,7 +104,7 @@ let benchmarks = {
try await channel.writeInbound(HTTPRequestPart.body(buffer))
try await channel.writeInbound(HTTPRequestPart.end(nil))
} responder: { request, _ in
let buffer = try await request.body.collate(maxSize: .max)
let buffer = try await request.body.collect(upTo: .max)
return .init(status: .ok, body: .init(byteBuffer: buffer))
}
}
3 changes: 2 additions & 1 deletion Benchmarks/Benchmarks/Router/Benchmarks.swift
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

import Benchmark
@testable import Hummingbird
import Hummingbird

let benchmarks = {
Benchmark.defaultConfiguration = .init(
Expand All @@ -25,5 +25,6 @@ let benchmarks = {
warmupIterations: 10
)
trieRouterBenchmarks()
binaryTrieRouterBenchmarks()
routerBenchmarks()
}
84 changes: 84 additions & 0 deletions Benchmarks/Benchmarks/Router/BinaryTrieRouterBenchmarks.swift
@@ -0,0 +1,84 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2021 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Benchmark
@_spi(Internal) import Hummingbird

func binaryTrieRouterBenchmarks() {
var trie: BinaryTrie<String>!
Benchmark("BinaryTrieRouter", configuration: .init(scalingFactor: .kilo)) { benchmark in
let testValues = [
"/test/",
"/test/one",
"/test/one/two",
"/doesntExist",
"/api/v1/users/1/profile",
]
benchmark.startMeasurement()

for _ in benchmark.scaledIterations {
blackHole(testValues.map { trie.resolve($0) })
}
} setup: {
let trieBuilder = RouterPathTrieBuilder<String>()
trieBuilder.addEntry("/test/", value: "/test/")
trieBuilder.addEntry("/test/one", value: "/test/one")
trieBuilder.addEntry("/test/one/two", value: "/test/one/two")
trieBuilder.addEntry("/test/:value", value: "/test/:value")
trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*")
trie = try! BinaryTrie(base: trieBuilder.build())
}

var trie2: BinaryTrie<String>!
Benchmark("BinaryTrieRouterParameters", configuration: .init(scalingFactor: .kilo)) { benchmark in
let testValues = [
"/test/value",
"/test/value1/value2",
"/test2/one/two",
"/api/v1/users/1/profile",
]
benchmark.startMeasurement()

for _ in benchmark.scaledIterations {
blackHole(testValues.map { trie2.resolve($0) })
}
} setup: {
let trieBuilder = RouterPathTrieBuilder<String>()
trieBuilder.addEntry("/test/:value", value: "/test/:value")
trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2")
trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trie2 = try! BinaryTrie(base: trieBuilder.build())
}

var trie3: BinaryTrie<String>!
Benchmark("BinaryTrie:LongPaths", configuration: .init(scalingFactor: .kilo)) { benchmark in
let testValues = [
"/api/v1/users/1/profile",
"/api/v1/a/very/long/path/with/lots/of/segments",
]
benchmark.startMeasurement()

for _ in benchmark.scaledIterations {
blackHole(testValues.map { trie3.resolve($0) })
}
} setup: {
let trieBuilder = RouterPathTrieBuilder<String>()
trieBuilder.addEntry("/api/v1/a/very/long/path/with/lots/of/segments", value: "/api/v1/a/very/long/path/with/lots/of/segments")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trie3 = try! BinaryTrie(base: trieBuilder.build())
}
}
37 changes: 18 additions & 19 deletions Benchmarks/Benchmarks/Router/RouterBenchmarks.swift
Expand Up @@ -15,6 +15,7 @@
import Benchmark
import HTTPTypes
import Hummingbird
import NIOEmbedded
import NIOHTTPTypes
@_spi(Internal) import HummingbirdCore
import Logging
Expand All @@ -35,14 +36,15 @@ struct BenchmarkBodyWriter: Sendable, ResponseBodyWriter {
func write(_: ByteBuffer) async throws {}
}

typealias ByteBufferWriter = (ByteBuffer) async throws -> Void
extension Benchmark {
@discardableResult
convenience init?<Context: RequestContext>(
name: String,
context: Context.Type = BasicBenchmarkContext.self,
configuration: Benchmark.Configuration = Benchmark.defaultConfiguration,
request: HTTPRequest,
writeBody: @escaping @Sendable (StreamedRequestBody.InboundStream.TestSource) async throws -> Void = { _ in },
writeBody: @escaping @Sendable (ByteBufferWriter) async throws -> Void = { _ in },
setupRouter: @escaping @Sendable (Router<Context>) async throws -> Void
) {
let router = Router(context: Context.self)
Expand All @@ -54,19 +56,16 @@ extension Benchmark {
for _ in 0..<50 {
try await withThrowingTaskGroup(of: Void.self) { group in
let context = Context(
allocator: ByteBufferAllocator(),
channel: EmbeddedChannel(),
logger: Logger(label: "Benchmark")
)
let (inbound, source) = NIOAsyncChannelInboundStream<HTTPRequestPart>.makeTestingStream()
let streamer = StreamedRequestBody(iterator: inbound.makeAsyncIterator())
let requestBody = RequestBody.stream(streamer)
let (requestBody, source) = RequestBody.makeStream()
let Request = Request(head: request, body: requestBody)
group.addTask {
let response = try await responder.respond(to: Request, context: context)
_ = try await response.body.write(BenchmarkBodyWriter())
}
try await writeBody(source)
source.yield(.end(nil))
try await writeBody(source.yield)
source.finish()
}
}
Expand Down Expand Up @@ -98,14 +97,14 @@ func routerBenchmarks() {
name: "Router:PUT",
configuration: .init(warmupIterations: 10),
request: .init(method: .put, scheme: "http", authority: "localhost", path: "/")
) { bodyStream in
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
) { write in
try await write(buffer)
try await write(buffer)
try await write(buffer)
try await write(buffer)
} setupRouter: { router in
router.put { request, _ in
let body = try await request.body.collate(maxSize: .max)
let body = try await request.body.collect(upTo: .max)
return body.readableBytes.description
}
}
Expand All @@ -114,11 +113,11 @@ func routerBenchmarks() {
name: "Router:Echo",
configuration: .init(warmupIterations: 10),
request: .init(method: .post, scheme: "http", authority: "localhost", path: "/")
) { bodyStream in
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
bodyStream.yield(.body(buffer))
) { write in
try await write(buffer)
try await write(buffer)
try await write(buffer)
try await write(buffer)
} setupRouter: { router in
router.post { request, _ in
Response(status: .ok, headers: [:], body: .init { writer in
Expand All @@ -134,7 +133,7 @@ func routerBenchmarks() {
configuration: .init(warmupIterations: 10),
request: .init(method: .get, scheme: "http", authority: "localhost", path: "/")
) { router in
struct EmptyMiddleware<Context>: MiddlewareProtocol {
struct EmptyMiddleware<Context>: RouterMiddleware {
func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response {
return try await next(request, context)
}
Expand Down
24 changes: 23 additions & 1 deletion Benchmarks/Benchmarks/Router/TrieRouterBenchmarks.swift
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

import Benchmark
@testable import Hummingbird
@_spi(Internal) import Hummingbird

func trieRouterBenchmarks() {
var trie: RouterPathTrie<String>!
Expand All @@ -23,6 +23,7 @@ func trieRouterBenchmarks() {
"/test/one",
"/test/one/two",
"/doesntExist",
"/api/v1/users/1/profile",
]
benchmark.startMeasurement()

Expand All @@ -36,6 +37,7 @@ func trieRouterBenchmarks() {
trieBuilder.addEntry("/test/one/two", value: "/test/one/two")
trieBuilder.addEntry("/test/:value", value: "/test/:value")
trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*")
trie = trieBuilder.build()
}
Expand All @@ -46,6 +48,7 @@ func trieRouterBenchmarks() {
"/test/value",
"/test/value1/value2",
"/test2/one/two",
"/api/v1/users/1/profile",
]
benchmark.startMeasurement()

Expand All @@ -57,6 +60,25 @@ func trieRouterBenchmarks() {
trieBuilder.addEntry("/test/:value", value: "/test/:value")
trieBuilder.addEntry("/test/:value/:value2", value: "/test/:value:/:value2")
trieBuilder.addEntry("/test2/*/*", value: "/test2/*/*")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trie2 = trieBuilder.build()
}

var trie3: RouterPathTrie<String>!
Benchmark("Trie:LongPaths", configuration: .init(scalingFactor: .kilo)) { benchmark in
let testValues = [
"/api/v1/users/1/profile",
"/api/v1/a/very/long/path/with/lots/of/segments",
]
benchmark.startMeasurement()

for _ in benchmark.scaledIterations {
blackHole(testValues.map { trie3.getValueAndParameters($0) })
}
} setup: {
let trieBuilder = RouterPathTrieBuilder<String>()
trieBuilder.addEntry("/api/v1/a/very/long/path/with/lots/of/segments", value: "/api/v1/a/very/long/path/with/lots/of/segments")
trieBuilder.addEntry("/api/v1/users/:id/profile", value: "/api/v1/users/:id/profile")
trie3 = trieBuilder.build()
}
}