Skip to content

Commit

Permalink
Directly parse memory without ByteBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
Joannis committed May 8, 2024
1 parent 31ab32f commit 48262a9
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 75 deletions.
64 changes: 34 additions & 30 deletions Sources/Hummingbird/Router/BinaryTrie/BinaryTrie+resolve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,35 @@ extension BinaryTrie {
/// Resolve a path to a `Value` if available
@inlinable
@_spi(Internal) public func resolve(_ path: String) -> (value: Value, parameters: Parameters)? {
var trie = trie
let pathComponents = path.split(separator: "/", omittingEmptySubsequences: true)
var pathComponentsIterator = pathComponents.makeIterator()
var parameters = Parameters()
guard var node: BinaryTrieNode = trie.readBinaryTrieNode() else { return nil }
while let component = pathComponentsIterator.next() {
node = self.matchComponent(component, in: &trie, parameters: &parameters)
if node.token == .recursiveWildcard {
// we have found a recursive wildcard. Go through all the path components until we match one of them
// or reach the end of the path component array
var range = component.startIndex..<component.endIndex
while let component = pathComponentsIterator.next() {
var recursiveTrie = trie
let recursiveNode = self.matchComponent(component, in: &recursiveTrie, parameters: &parameters)
if recursiveNode.token != .deadEnd {
node = recursiveNode
break
return trie.withParsingContext { trie in
let pathComponents = path.split(separator: "/", omittingEmptySubsequences: true)
var pathComponentsIterator = pathComponents.makeIterator()
var parameters = Parameters()
var node: BinaryTrieNode = trie.readBinaryTrieNode()
while let component = pathComponentsIterator.next() {
node = self.matchComponent(component, in: &trie, parameters: &parameters)
if node.token == .recursiveWildcard {
// we have found a recursive wildcard. Go through all the path components until we match one of them
// or reach the end of the path component array
var range = component.startIndex..<component.endIndex
while let component = pathComponentsIterator.next() {
var recursiveTrie = trie
let recursiveNode = self.matchComponent(component, in: &recursiveTrie, parameters: &parameters)
if recursiveNode.token != .deadEnd {
node = recursiveNode
break
}
// extend range of catch all text
range = range.lowerBound..<component.endIndex
}
// extend range of catch all text
range = range.lowerBound..<component.endIndex
parameters.setCatchAll(path[range])
}
if node.token == .deadEnd {
return nil
}
parameters.setCatchAll(path[range])
}
if node.token == .deadEnd {
return nil
}
return self.value(for: node.index, parameters: parameters)
}
return self.value(for: node.index, parameters: parameters)
}

/// If `index != nil`, resolves the `index` to a `Value`
Expand All @@ -63,20 +64,21 @@ extension BinaryTrie {
@usableFromInline
internal func matchComponent(
_ component: Substring,
in trie: inout ByteBuffer,
in trie: inout Trie.ParsingContext,
parameters: inout Parameters
) -> BinaryTrieNode {
while let node = trie.readBinaryTrieNode() {
while !trie.isAtEnd {
let node = trie.readBinaryTrieNode()
let result = self.matchComponent(component, withToken: node.token, in: &trie, parameters: &parameters)
switch result {
case .match, .deadEnd:
return node
default:
trie.moveReaderIndex(to: Int(node.nextSiblingNodeIndex))
trie.byteOffset = Int(node.nextSiblingNodeIndex)
}
}
// should never get here
return .init(index: 0, token: .deadEnd, nextSiblingNodeIndex: UInt32(trie.writerIndex))
return .init(index: 0, token: .deadEnd, nextSiblingNodeIndex: UInt32(trie.buffer.count))
}

@usableFromInline
Expand All @@ -88,7 +90,7 @@ extension BinaryTrie {
internal func matchComponent(
_ component: Substring,
withToken token: BinaryTrieTokenKind,
in trie: inout ByteBuffer,
in trie: inout Trie.ParsingContext,
parameters: inout Parameters
) -> MatchResult {
switch token {
Expand All @@ -107,7 +109,9 @@ extension BinaryTrie {
case .capture:
// The current node is a parameter
guard
let parameter = trie.readLengthPrefixedString(as: Integer.self)
let parameter = trie.readLengthPrefixedString(
as: Integer.self
)
else {
return .mismatch
}
Expand Down
70 changes: 68 additions & 2 deletions Sources/Hummingbird/Router/BinaryTrie/BinaryTrie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,61 @@ struct BinaryTrieNode {
static let serializedSize = 7
}

@usableFromInline
struct Trie: @unchecked Sendable {
@usableFromInline
let trie: ManagedBuffer<Void, UInt8>

@inlinable
init(trie: ManagedBuffer<Void, UInt8>) {
self.trie = trie
}

@usableFromInline
func withParsingContext<T>(
_ perform: (inout ParsingContext) throws -> T
) rethrows -> T {
let byteSize = trie.capacity
return try trie.withUnsafeMutablePointerToElements { pointer in
var context = ParsingContext(
buffer: UnsafeRawBufferPointer(
start: pointer,
count: byteSize
),
byteOffset: 0
)

return try perform(&context)
}
}

@usableFromInline
struct ParsingContext {
@usableFromInline
let buffer: UnsafeRawBufferPointer

@usableFromInline
var byteOffset: Int

@usableFromInline
init(buffer: UnsafeRawBufferPointer, byteOffset: Int) {
self.buffer = buffer
self.byteOffset = byteOffset
}

@usableFromInline
var isAtEnd: Bool {
byteOffset >= buffer.count
}
}
}

@_spi(Internal) public final class BinaryTrie<Value: Sendable>: Sendable {
@usableFromInline
typealias Integer = UInt8

@usableFromInline
let trie: ByteBuffer
let trie: Trie

@usableFromInline
let values: [Value?]
Expand All @@ -55,7 +104,24 @@ struct BinaryTrieNode {
values: &values
)

self.trie = trie
let buffer = ManagedBuffer<Void, UInt8>.create(
minimumCapacity: trie.readableBytes
) { managedBuffer in
return ()
}

buffer.withUnsafeMutablePointerToElements { destination in
trie.withUnsafeReadableBytes { source in
_ = source.copyBytes(
to: UnsafeMutableRawBufferPointer(
start: destination,
count: source.count
)
)
}
}

self.trie = Trie(trie: buffer)
self.values = values
}
}
89 changes: 46 additions & 43 deletions Sources/Hummingbird/Router/BinaryTrie/ByteBuffer+BinaryTrie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import Glibc

internal extension ByteBuffer {
/// Write length prefixed string to ByteBuffer
@inlinable
mutating func writeLengthPrefixedString<F: FixedWidthInteger>(_ string: Substring, as integer: F.Type) {
do {
try self.writeLengthPrefixed(as: F.self) { buffer in
try self.writeLengthPrefixed(endianness: .host, as: F.self) { buffer in
buffer.writeSubstring(string)
}
} catch {
Expand All @@ -36,9 +37,9 @@ internal extension ByteBuffer {

/// Write BinaryTrieNode into ByteBuffer at position
@discardableResult mutating func setBinaryTrieNode(_ node: BinaryTrieNode, at index: Int) -> Int {
var offset = self.setInteger(node.index, at: index)
offset += self.setInteger(node.token.rawValue, at: index + offset)
offset += self.setInteger(node.nextSiblingNodeIndex, at: index + offset)
var offset = self.setInteger(node.index, at: index, endianness: .host)
offset += self.setInteger(node.token.rawValue, at: index + offset, endianness: .host)
offset += self.setInteger(node.nextSiblingNodeIndex, at: index + offset, endianness: .host)
return offset
}

Expand All @@ -52,31 +53,32 @@ internal extension ByteBuffer {
mutating func reserveBinaryTrieNode() {
self.moveWriterIndex(forwardBy: BinaryTrieNode.serializedSize)
}
}

extension Trie.ParsingContext {
/// Read BinaryTrieNode from ByteBuffer
@usableFromInline
mutating func readBinaryTrieNode() -> BinaryTrieNode? {
guard let index = self.readInteger(as: UInt16.self),
let token = self.readToken(),
let nextSiblingNodeIndex: UInt32 = self.readInteger()
else {
return nil
}
mutating func readBinaryTrieNode() -> BinaryTrieNode {
let index = buffer.loadUnaligned(fromByteOffset: byteOffset, as: UInt16.self)
byteOffset &+= 2

let token = buffer.loadUnaligned(fromByteOffset: byteOffset, as: BinaryTrieTokenKind.self)
byteOffset &+= 1

let nextSiblingNodeIndex = buffer.loadUnaligned(fromByteOffset: byteOffset, as: UInt32.self)
byteOffset &+= 4

return BinaryTrieNode(index: index, token: token, nextSiblingNodeIndex: nextSiblingNodeIndex)
}

/// Read string from ByteBuffer and compare against another string
@inlinable
mutating func readAndCompareString<Length: FixedWidthInteger>(
to string: Substring,
length: Length.Type
) -> Bool {
guard
let _length: Length = readInteger()
else {
return false
}

let length = Int(_length)
let length = Int(buffer.loadUnaligned(fromByteOffset: byteOffset, as: Length.self))
byteOffset &+= Length.bitWidth / 8

func compare(utf8: UnsafeBufferPointer<UInt8>) -> Bool {
if utf8.count != length {
Expand All @@ -89,13 +91,15 @@ internal extension ByteBuffer {
return true
}

return withUnsafeReadableBytes { buffer in
if memcmp(utf8.baseAddress!, buffer.baseAddress!, length) == 0 {
moveReaderIndex(forwardBy: length)
return true
} else {
return false
}
if memcmp(
utf8.baseAddress!,
buffer.baseAddress!.advanced(by: byteOffset),
length
) == 0 {
byteOffset &+= length
return true
} else {
return false
}
}

Expand All @@ -114,23 +118,22 @@ internal extension ByteBuffer {
}

/// Read length prefixed string from ByteBuffer
mutating func readLengthPrefixedString<F: FixedWidthInteger>(as integer: F.Type) -> String? {
guard let buffer = readLengthPrefixedSlice(as: F.self) else {
return nil
}

return String(buffer: buffer)
}

/// Read BinaryTrieTokenKind from ByteBuffer
mutating func readToken() -> BinaryTrieTokenKind? {
guard
let _token: BinaryTrieTokenKind.RawValue = readInteger(),
let token = BinaryTrieTokenKind(rawValue: _token)
else {
return nil
}

return token
@inlinable
mutating func readLengthPrefixedString<F: FixedWidthInteger>(
as integer: F.Type
) -> String? {
let lengthPrefix = buffer.loadUnaligned(fromByteOffset: byteOffset, as: F.self)
byteOffset &+= (F.bitWidth / 8)
let string = String(
bytes: UnsafeRawBufferPointer(
start: buffer.baseAddress!.advanced(
by: byteOffset
),
count: Int(lengthPrefix)
),
encoding: .utf8
)
byteOffset &+= Int(lengthPrefix)
return string
}
}

0 comments on commit 48262a9

Please sign in to comment.