diff --git a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift index 4607f5809..aa085686f 100644 --- a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift +++ b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift @@ -97,6 +97,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(ByteBuffer(assumingMemoryBound: memory, capacity: Int(oneGB))) } + benchmark.stopMeasurement() } Benchmark("Clearing 1GB", configuration: singleConfiguration) { benchmark in @@ -105,6 +106,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.clear()) } + benchmark.stopMeasurement() } Benchmark("Strings 10") { benchmark in @@ -113,6 +115,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.create(string: str10)) } + benchmark.stopMeasurement() } Benchmark("Strings 100") { benchmark in @@ -121,6 +124,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.create(string: str100)) } + benchmark.stopMeasurement() } Benchmark("Vector 1 Bytes") { benchmark in @@ -129,6 +133,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.createVector(bytes: bytes)) } + benchmark.stopMeasurement() } Benchmark("Vector 1 Ints") { benchmark in @@ -137,6 +142,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.createVector(ints)) } + benchmark.stopMeasurement() } Benchmark("Vector 100 Ints") { benchmark in @@ -145,6 +151,7 @@ let benchmarks = { for i in benchmark.scaledIterations { blackHole(fb.createVector(ints)) } + benchmark.stopMeasurement() } Benchmark("Vector 100 Bytes") { benchmark in @@ -153,6 +160,7 @@ let benchmarks = { for i in benchmark.scaledIterations { blackHole(fb.createVector(bytes)) } + benchmark.stopMeasurement() } Benchmark("Vector 100 ContiguousBytes") { benchmark in @@ -161,6 +169,7 @@ let benchmarks = { for i in benchmark.scaledIterations { blackHole(fb.createVector(bytes: bytes)) } + benchmark.stopMeasurement() } Benchmark( @@ -178,6 +187,7 @@ let benchmarks = { fb.add(offset: off, at: 8) blackHole(fb.endTable(at: s)) } + benchmark.stopMeasurement() } Benchmark( @@ -190,6 +200,7 @@ let benchmarks = { let s = fb.startTable(with: 4) blackHole(fb.endTable(at: s)) } + benchmark.stopMeasurement() } Benchmark("Struct") { benchmark in @@ -198,6 +209,7 @@ let benchmarks = { for _ in benchmark.scaledIterations { blackHole(fb.create(struct: array.first!)) } + benchmark.stopMeasurement() } Benchmark("Structs") { benchmark in @@ -219,6 +231,7 @@ let benchmarks = { fb.add(offset: vector, at: 4) let root = Offset(offset: fb.endTable(at: start)) blackHole(fb.finish(offset: root)) + benchmark.stopMeasurement() } Benchmark("Vector of Offsets") { benchmark in @@ -239,12 +252,15 @@ let benchmarks = { fb.add(offset: off, at: 2) blackHole(fb.endTable(at: s)) } + benchmark.stopMeasurement() } Benchmark("Reading Doubles") { benchmark in let byteBuffer = ByteBuffer(data: data) + benchmark.startMeasurement() for _ in benchmark.scaledIterations { blackHole(byteBuffer.read(def: Double.self, position: 0)) } + benchmark.stopMeasurement() } } diff --git a/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift new file mode 100644 index 000000000..22d0d5413 --- /dev/null +++ b/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift @@ -0,0 +1,174 @@ +/* + * Copyright 2024 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Benchmark +import FlexBuffers +import Foundation + +let benchmarks = { + let data = { + var array = [8888.88, 8888.88] + var data = Data() + array.withUnsafeBytes { ptr in + data.append(contentsOf: ptr) + } + return data + }() + let ints: [Int32] = Array(repeating: 42, count: 100) + let str10 = (0...9).map { _ -> String in "x" }.joined() + let str100 = (0...99).map { _ -> String in "x" }.joined() + + // A representative map: 50 keyed scalars, a keyed string and a keyed vector + // of 100 scalars. Used for the realistic decode benchmarks. + let mapBuffer: ByteBuffer = { + var fbx = FlexBuffersWriter(initialSize: 1 << 16) + fbx.map { + for i in 0..<50 { $0.add(int: i, key: "i\(i)") } + $0.add(string: "hello world", key: "s") + $0.vector(key: "v") { v in + for x in 0..<100 { v.add(int: x) } + } + } + fbx.finish() + return fbx.sizedByteBuffer + }() + + let metrics: [BenchmarkMetric] = [ + .cpuTotal, + .wallClock, + .mallocCountTotal, + .releaseCount, + .peakMemoryResident, + ] + let maxIterations = 1_000_000 + let maxDuration: Duration = .seconds(3) + let megaConfiguration: Benchmark.Configuration = .init( + metrics: metrics, + warmupIterations: 1, + scalingFactor: .mega, + maxDuration: maxDuration, + maxIterations: maxIterations) + + Benchmark.defaultConfiguration = megaConfiguration + + // Decode (read path) + + // Raw scalar read: isolates `read` and the `let` blob. + Benchmark("Reading Doubles") { benchmark in + let byteBuffer = ByteBuffer(data: data) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(byteBuffer.read(def: Double.self, position: 0)) + } + benchmark.stopMeasurement() + } + + // Realistic decode: resolve root map and read a keyed scalar. + Benchmark("Decode Map Scalar") { benchmark in + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + let map = try! getRoot(buffer: mapBuffer)!.map! + blackHole(map["i25"]?.int) + } + benchmark.stopMeasurement() + } + + // Realistic decode: resolve root map and read a keyed string. + Benchmark("Decode Map String") { benchmark in + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + let map = try! getRoot(buffer: mapBuffer)!.map! + blackHole(map["s"]?.string()) + } + benchmark.stopMeasurement() + } + + // Realistic decode: resolve a nested vector and sum its scalars. + Benchmark("Decode Vector") { benchmark in + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + let map = try! getRoot(buffer: mapBuffer)!.map! + let vector = map["v"]!.vector! + var sum: Int64 = 0 + for i in 0..( position: Int, _ body: (UnsafeRawPointer) throws -> T) rethrows -> T @@ -278,7 +313,7 @@ public struct ByteBuffer: @unchecked Sendable { /// - removeBytes: Removes a number of bytes from the current size @inline(__always) init( - blob: Storage.Blob, + blob: borrowing Storage.Blob, count: Int, removing removeBytes: Int) { @@ -318,7 +353,8 @@ public struct ByteBuffer: @unchecked Sendable { /// - def: Type of the object /// - position: the index of the object in the buffer @inline(__always) - public func read(def: T.Type, position: Int) -> T { + @inlinable + public func read(def: T.Type, position: Int) -> T { _storage.readWithUnsafeRawPointer(position: position) { $0.bindMemory(to: T.self, capacity: 1) .pointee @@ -412,7 +448,7 @@ public struct ByteBuffer: @unchecked Sendable { /// - Parameter removeBytes: the amount of bytes to remove from the current Size @inline(__always) public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer { - assert(removeBytes > 0, "Can NOT remove negative bytes") + assert(removeBytes >= 0, "Can NOT remove negative bytes") assert( removeBytes < capacity, "Can NOT remove more bytes than the ones allocated") @@ -464,8 +500,9 @@ public struct ByteBuffer: @unchecked Sendable { extension ByteBuffer: CustomDebugStringConvertible { public var debugDescription: String { - """ - buffer located at: \(_storage.retainedBlob), + let blobDescription = _storage.retainedBlob.description + return """ + buffer located at: \(blobDescription), with capacity of \(capacity), { writtenSize: \(_readerIndex), readerSize: \(reader), size: \(size) } diff --git a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift index b8e49bd4e..d9cc0a96d 100644 --- a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift +++ b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift @@ -47,7 +47,8 @@ public struct FlatBufferBuilder { /// A check to see if finish(::) was ever called to retreive data object private(set) var finished = false /// A check to see if the buffer should serialize Default values - private var serializeDefaults: Bool + @usableFromInline + let serializeDefaults: Bool /// Current alignment for the buffer var _minAlignment: Int = 0 { @@ -756,6 +757,7 @@ public struct FlatBufferBuilder { /// - offset: ``Offset`` of another object to be written /// - position: The predefined position of the object @inline(__always) + @inlinable mutating public func add(offset: Offset, at position: VOffset) { if offset.isEmpty { return } add(element: refer(to: offset.o), def: 0, at: position) @@ -794,6 +796,7 @@ public struct FlatBufferBuilder { /// - def: Default value for that element /// - position: The predefined position of the element @inline(__always) + @inlinable mutating public func add( element: T, def: T, @@ -813,6 +816,7 @@ public struct FlatBufferBuilder { /// - element: Optional element of type scalar /// - position: The predefined position of the element @inline(__always) + @inlinable mutating public func add(element: T?, at position: VOffset) { guard let element = element else { return } track(offset: push(element: element), at: position) @@ -825,6 +829,7 @@ public struct FlatBufferBuilder { /// - Parameter element: Element to insert /// - returns: position of the Element @inline(__always) + @inlinable @discardableResult mutating public func push(element: T) -> UOffset { let size = MemoryLayout.size @@ -836,7 +841,8 @@ public struct FlatBufferBuilder { } @inline(__always) - public func read(def: T.Type, position: Int) -> T { + @inlinable + public func read(def: T.Type, position: Int) -> T { _bb.read(def: def, position: position) } } diff --git a/swift/Sources/FlatBuffers/FlatBufferObject.swift b/swift/Sources/FlatBuffers/FlatBufferObject.swift index a85f10736..dce45128e 100644 --- a/swift/Sources/FlatBuffers/FlatBufferObject.swift +++ b/swift/Sources/FlatBuffers/FlatBufferObject.swift @@ -18,7 +18,7 @@ import Foundation /// NativeStruct is a protocol that indicates if the struct is a native `swift` struct /// since now we will be serializing native structs into the buffer. -public protocol NativeStruct {} +public protocol NativeStruct: BitwiseCopyable {} public protocol FlatBufferVerifiableNativeStruct: NativeStruct, Verifiable {} diff --git a/swift/Sources/FlatBuffers/Table.swift b/swift/Sources/FlatBuffers/Table.swift index f29a51e5b..de8762654 100644 --- a/swift/Sources/FlatBuffers/Table.swift +++ b/swift/Sources/FlatBuffers/Table.swift @@ -82,7 +82,7 @@ public struct Table { /// - Parameters: /// - type: Type of Element that needs to be read from the buffer /// - o: Offset of the Element - public func readBuffer(of type: T.Type, at o: Int32) -> T { + public func readBuffer(of type: T.Type, at o: Int32) -> T { bb.read(def: T.self, position: Int(o &+ position)) } diff --git a/swift/Sources/FlatBuffers/Verifier.swift b/swift/Sources/FlatBuffers/Verifier.swift index b0ef3968d..525e14942 100644 --- a/swift/Sources/FlatBuffers/Verifier.swift +++ b/swift/Sources/FlatBuffers/Verifier.swift @@ -163,7 +163,7 @@ public struct Verifier { /// - Parameter position: Current position to be read /// - Throws: `inBuffer` errors /// - Returns: a value of type `T` usually a `VTable` or a table offset - internal func getValue(at position: Int) throws -> T { + internal func getValue(at position: Int) throws -> T { try inBuffer(position: position, of: T.self) return _buffer.read(def: T.self, position: position) } diff --git a/swift/Sources/FlatBuffers/_InternalByteBuffer.swift b/swift/Sources/FlatBuffers/_InternalByteBuffer.swift index e4ed2f08d..a3d730855 100644 --- a/swift/Sources/FlatBuffers/_InternalByteBuffer.swift +++ b/swift/Sources/FlatBuffers/_InternalByteBuffer.swift @@ -30,7 +30,7 @@ struct _InternalByteBuffer { @usableFromInline final class Storage { /// pointer to the start of the buffer object in memory - private(set) var memory: UnsafeMutableRawPointer + @exclusivity(unchecked) private(set) var memory: UnsafeMutableRawPointer @usableFromInline init(count: Int, alignment: Int) { @@ -333,6 +333,7 @@ struct _InternalByteBuffer { @discardableResult @inline(__always) + @usableFromInline func readWithUnsafeRawPointer( position: Int, _ body: (UnsafeRawPointer) throws -> T) rethrows -> T diff --git a/swift/Sources/FlexBuffers/ByteBuffer.swift b/swift/Sources/FlexBuffers/ByteBuffer.swift index f4dd2adb7..6789618ce 100644 --- a/swift/Sources/FlexBuffers/ByteBuffer.swift +++ b/swift/Sources/FlexBuffers/ByteBuffer.swift @@ -27,7 +27,7 @@ public struct ByteBuffer { @usableFromInline final class Storage { @usableFromInline - enum Blob { + @frozen enum Blob: ~Copyable { #if !os(WASI) case data(Data) case bytes(any ContiguousBytes) @@ -36,6 +36,23 @@ public struct ByteBuffer { case byteBuffer(_InternalByteBuffer) case array([UInt8]) case pointer(UnsafeMutableRawPointer) + + init(_ other: borrowing Blob) { + switch other { + #if !os(WASI) + case .data(let data): + self = .data(data) + case .bytes(let contiguousBytes): + self = .bytes(contiguousBytes) + #endif + case .byteBuffer(let internalByteBuffer): + self = .byteBuffer(internalByteBuffer) + case .array(let array): + self = .array(array) + case .pointer(let unsafeMutableRawPointer): + self = .pointer(unsafeMutableRawPointer) + } + } } /// This storage doesn't own the memory, therefore, we won't deallocate on deinit. @@ -44,7 +61,7 @@ public struct ByteBuffer { private let capacity: Int /// Retained blob of data that requires the storage to retain a pointer to. @usableFromInline - var retainedBlob: Blob + let retainedBlob: Blob @usableFromInline init(count: Int) { @@ -57,9 +74,9 @@ public struct ByteBuffer { } @usableFromInline - init(blob: Blob, capacity count: Int) { + init(blob: borrowing Blob, capacity count: Int) { capacity = count - retainedBlob = blob + retainedBlob = .init(blob) isOwned = false } @@ -276,7 +293,7 @@ public struct ByteBuffer { /// - removeBytes: Removes a number of bytes from the current size @inline(__always) init( - blob: Storage.Blob, + blob: borrowing Storage.Blob, count: Int, removing removeBytes: Int) { @@ -316,7 +333,7 @@ public struct ByteBuffer { /// - def: Type of the object /// - position: the index of the object in the buffer @inline(__always) - public func read(def: T.Type, position: Int) -> T { + public func read(def: T.Type, position: Int) -> T { _storage.readWithUnsafeRawPointer(position: position) { $0.bindMemory(to: T.self, capacity: 1) .pointee @@ -360,10 +377,10 @@ public struct ByteBuffer { @inline(__always) func readSizedScalar< T: BinaryInteger, - T1: BinaryInteger, - T2: BinaryInteger, - T3: BinaryInteger, - T4: BinaryInteger + T1: BinaryInteger & BitwiseCopyable, + T2: BinaryInteger & BitwiseCopyable, + T3: BinaryInteger & BitwiseCopyable, + T4: BinaryInteger & BitwiseCopyable >( def: T.Type, t1: T1.Type, diff --git a/swift/Sources/FlexBuffers/_InternalByteBuffer.swift b/swift/Sources/FlexBuffers/_InternalByteBuffer.swift index 5df4ff31a..e99f95aa1 100644 --- a/swift/Sources/FlexBuffers/_InternalByteBuffer.swift +++ b/swift/Sources/FlexBuffers/_InternalByteBuffer.swift @@ -31,7 +31,7 @@ struct _InternalByteBuffer { @usableFromInline final class Storage { /// pointer to the start of the buffer object in memory - var memory: UnsafeMutableRawPointer + @exclusivity(unchecked) private(set) var memory: UnsafeMutableRawPointer @usableFromInline init(count: Int, alignment: Int) {