diff --git a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift new file mode 100644 index 000000000..902ff475a --- /dev/null +++ b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift @@ -0,0 +1,181 @@ +/* + * Copyright 2023 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 CoreFoundation +import FlatBuffers + +@usableFromInline +struct AA: NativeStruct { + public init(a: Double, b: Double) { + self.a = a + self.b = b + } + var a: Double + var b: Double +} + +let benchmarks = { + let ints: [Int] = Array(repeating: 42, count: 100) + let bytes: [UInt8] = Array(repeating: 42, count: 100) + let str10 = (0...9).map { _ -> String in "x" }.joined() + let str100 = (0...99).map { _ -> String in "x" }.joined() + let array: [AA] = [ + AA(a: 2.4, b: 2.4), + AA(a: 2.4, b: 2.4), + AA(a: 2.4, b: 2.4), + AA(a: 2.4, b: 2.4), + AA(a: 2.4, b: 2.4), + ] + + let metrics: [BenchmarkMetric] = [ + .cpuTotal, + .wallClock, + .mallocCountTotal, + .releaseCount, + .peakMemoryResident, + ] + let maxIterations = 1_000_000 + let maxDuration: Duration = .seconds(3) + let singleConfiguration: Benchmark.Configuration = .init( + metrics: metrics, + warmupIterations: 1, + scalingFactor: .one, + maxDuration: maxDuration, + maxIterations: maxIterations) + let kiloConfiguration: Benchmark.Configuration = .init( + metrics: metrics, + warmupIterations: 1, + scalingFactor: .kilo, + maxDuration: maxDuration, + maxIterations: maxIterations) + let megaConfiguration: Benchmark.Configuration = .init( + metrics: metrics, + warmupIterations: 1, + scalingFactor: .mega, + maxDuration: maxDuration, + maxIterations: maxIterations) + + Benchmark.defaultConfiguration = megaConfiguration + + Benchmark("Allocating 1GB", configuration: singleConfiguration) { benchmark in + for _ in benchmark.scaledIterations { + blackHole(FlatBufferBuilder(initialSize: 1_024_000_000)) + } + } + + Benchmark("Clearing 1GB", configuration: singleConfiguration) { benchmark in + var fb = FlatBufferBuilder(initialSize: 1_024_000_000) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(fb.clear()) + } + } + + Benchmark("Strings 10") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(fb.create(string: str10)) + } + } + + Benchmark("Strings 100") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(fb.create(string: str100)) + } + } + + Benchmark("Vector 1 Bytes") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(fb.createVector(bytes: bytes)) + } + } + + Benchmark("Vector 1 Ints") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + blackHole(fb.createVector(ints)) + } + } + + Benchmark("Vector 100 Ints") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for i in benchmark.scaledIterations { + blackHole(fb.createVector(ints)) + } + } + + Benchmark("Vector 100 Bytes") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for i in benchmark.scaledIterations { + blackHole(fb.createVector(bytes)) + } + } + + Benchmark("Vector 100 ContiguousBytes") { benchmark in + var fb = FlatBufferBuilder(initialSize: 1<<20) + benchmark.startMeasurement() + for i in benchmark.scaledIterations { + blackHole(fb.createVector(bytes: bytes)) + } + } + + Benchmark( + "FlatBufferBuilder Add", + configuration: kiloConfiguration) + { benchmark in + var fb = FlatBufferBuilder(initialSize: 1024 * 1024 * 32) + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + let off = fb.create(string: "T") + let s = fb.startTable(with: 4) + fb.add(element: 3.2, def: 0, at: 2) + fb.add(element: 4.2, def: 0, at: 4) + fb.add(element: 5.2, def: 0, at: 6) + fb.add(offset: off, at: 8) + blackHole(fb.endTable(at: s)) + } + } + + Benchmark("Structs") { benchmark in + let rawSize = ((16 * 5) * benchmark.scaledIterations.count) / 1024 + var fb = FlatBufferBuilder(initialSize: Int32(rawSize * 1600)) + var offsets: [Offset] = [] + + benchmark.startMeasurement() + for _ in benchmark.scaledIterations { + let vector = fb.createVector( + ofStructs: array) + let start = fb.startTable(with: 1) + fb.add(offset: vector, at: 4) + offsets.append(Offset(offset: fb.endTable(at: start))) + } + + let vector = fb.createVector(ofOffsets: offsets) + let start = fb.startTable(with: 1) + fb.add(offset: vector, at: 4) + let root = Offset(offset: fb.endTable(at: start)) + fb.finish(offset: root) + } +} diff --git a/tests/swift/benchmarks/Package.swift b/benchmarks/swift/Package.swift similarity index 58% rename from tests/swift/benchmarks/Package.swift rename to benchmarks/swift/Package.swift index c4913b784..4204c4813 100644 --- a/tests/swift/benchmarks/Package.swift +++ b/benchmarks/swift/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.8 /* * Copyright 2020 Google Inc. All rights reserved. * @@ -20,15 +20,23 @@ import PackageDescription let package = Package( name: "benchmarks", platforms: [ - .macOS(.v10_14), + .macOS(.v13), ], dependencies: [ - .package(path: "../../.."), - .package(url: "https://github.com/google/swift-benchmark", from: "0.1.0"), + .package(path: "../.."), + .package( + url: "https://github.com/ordo-one/package-benchmark", + from: "1.12.0"), ], targets: [ - .target( - name: "benchmarks", - dependencies: ["FlatBuffers", - .product(name: "Benchmark", package: "swift-benchmark")]), + .executableTarget( + name: "FlatbuffersBenchmarks", + dependencies: [ + .product(name: "FlatBuffers", package: "flatbuffers"), + .product(name: "Benchmark", package: "package-benchmark"), + ], + path: "Benchmarks/FlatbuffersBenchmarks", + plugins: [ + .plugin(name: "BenchmarkPlugin", package: "package-benchmark"), + ]), ]) diff --git a/benchmarks/swift/README.md b/benchmarks/swift/README.md new file mode 100644 index 000000000..4f95872d6 --- /dev/null +++ b/benchmarks/swift/README.md @@ -0,0 +1,9 @@ +# Benchmarks + +To open the benchmarks in xcode use: + +`open --env BENCHMARK_DISABLE_JEMALLOC=true Package.swift` + +or running them directly within terminal using: + +`swift package benchmark` \ No newline at end of file diff --git a/swift/Sources/FlatBuffers/ByteBuffer.swift b/swift/Sources/FlatBuffers/ByteBuffer.swift index ef7227211..d33574d8b 100644 --- a/swift/Sources/FlatBuffers/ByteBuffer.swift +++ b/swift/Sources/FlatBuffers/ByteBuffer.swift @@ -261,6 +261,20 @@ public struct ByteBuffer { } } + /// Adds an array of type Scalar to the buffer memory + /// - Parameter elements: An array of Scalars + @inline(__always) + @usableFromInline + mutating func push(elements: [T]) { + elements.withUnsafeBytes { ptr in + ensureSpace(size: ptr.count) + _storage.memory + .advanced(by: writerIndex &- ptr.count) + .copyMemory(from: ptr.baseAddress!, byteCount: ptr.count) + self._writerSize = self._writerSize &+ ptr.count + } + } + /// Adds a `ContiguousBytes` to buffer memory /// - Parameter value: bytes to copy #if swift(>=5.0) && !os(WASI) @@ -271,8 +285,8 @@ public struct ByteBuffer { ensureSpace(size: ptr.count) memcpy( _storage.memory.advanced(by: writerIndex &- ptr.count), - UnsafeRawPointer(ptr.baseAddress!), - ptr.count) + UnsafeRawPointer(ptr.baseAddress!), + ptr.count) self._writerSize = self._writerSize &+ ptr.count } } diff --git a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift index 1fb66d55a..bf1978e33 100644 --- a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift +++ b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift @@ -623,9 +623,7 @@ public struct FlatBufferBuilder { startVector( structs.count * MemoryLayout.size, elementSize: MemoryLayout.alignment) - for i in structs.reversed() { - _ = create(struct: i) - } + _bb.push(elements: structs) return endVector(len: structs.count) } diff --git a/swift/Sources/FlatBuffers/Verifiable.swift b/swift/Sources/FlatBuffers/Verifiable.swift index 09f1a354d..f16d42172 100644 --- a/swift/Sources/FlatBuffers/Verifiable.swift +++ b/swift/Sources/FlatBuffers/Verifiable.swift @@ -129,7 +129,9 @@ public enum Vector: Verifiable where U: Verifiable, S: Verifiable { let range = try verifyRange(&verifier, at: position, of: UOffset.self) for index in stride( from: range.start, - to: Int(clamping: range.start &+ (range.count &* MemoryLayout.size)), + to: Int( + clamping: range + .start &+ (range.count &* MemoryLayout.size)), by: MemoryLayout.size) { try U.verify(&verifier, at: index, of: U.self) diff --git a/tests/swift/benchmarks/Sources/benchmarks/main.swift b/tests/swift/benchmarks/Sources/benchmarks/main.swift deleted file mode 100644 index b368add58..000000000 --- a/tests/swift/benchmarks/Sources/benchmarks/main.swift +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 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 CoreFoundation -import FlatBuffers - -benchmark("10Strings") { - var fb = FlatBufferBuilder(initialSize: 1<<20) - for _ in 0..<1_000_000 { - _ = fb.create(string: "foobarbaz") - } -} - -benchmark("100Strings") { - var fb = FlatBufferBuilder(initialSize: 1<<20) - for _ in 0..<1_000_000 { - _ = fb.create(string: str) - } -} - -benchmark("100Bytes") { - var fb = FlatBufferBuilder(initialSize: 1<<20) - for _ in 0..<1_000_000 { - _ = fb.createVector(bytes) - } -} - -benchmark("100Bytes-ContiguousBytes") { - var fb = FlatBufferBuilder(initialSize: 1<<20) - for _ in 0..<1_000_000 { - _ = fb.createVector(bytes: bytes) - } -} - -benchmark("FlatBufferBuilder.add") { - var fb = FlatBufferBuilder(initialSize: 1024 * 1024 * 32) - for _ in 0..<1_000_000 { - let off = fb.create(string: "T") - let s = fb.startTable(with: 4) - fb.add(element: 3.2, def: 0, at: 2) - fb.add(element: 4.2, def: 0, at: 4) - fb.add(element: 5.2, def: 0, at: 6) - fb.add(offset: off, at: 8) - _ = fb.endTable(at: s) - } -} - -benchmark("structs") { - let structCount = 1_000_000 - - let rawSize = ((16 * 5) * structCount) / 1024 - - var fb = FlatBufferBuilder(initialSize: Int32(rawSize * 1600)) - - var offsets: [Offset] = [] - for _ in 0...size, - elementSize: MemoryLayout.alignment) - for _ in 0..<5 { - _ = fb.create(struct: AA(a: 2.4, b: 2.4)) - } - let vector = fb.endVector(len: 5) - let start = fb.startTable(with: 1) - fb.add(offset: vector, at: 4) - offsets.append(Offset(offset: fb.endTable(at: start))) - } - let vector = fb.createVector(ofOffsets: offsets) - let start = fb.startTable(with: 1) - fb.add(offset: vector, at: 4) - let root = Offset(offset: fb.endTable(at: start)) - fb.finish(offset: root) -} - -let str = (0...99).map { _ -> String in "x" }.joined() -let bytes: [UInt8] = Array(repeating: 42, count: 100) - -@usableFromInline -struct AA: NativeStruct { - public init(a: Double, b: Double) { - self.a = a - self.b = b - } - var a: Double - var b: Double -} - -Benchmark.main() diff --git a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersNanInfTests.swift b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersNanInfTests.swift index 96b5614e6..07cdeb010 100644 --- a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersNanInfTests.swift +++ b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersNanInfTests.swift @@ -67,9 +67,9 @@ final class FlatBuffersNanInfTests: XCTestCase { let data = try encoder.encode(reader) let decoder = JSONDecoder() decoder.nonConformingFloatDecodingStrategy = .convertFromString( - positiveInfinity: "inf", - negativeInfinity: "-inf", - nan: "nan") + positiveInfinity: "inf", + negativeInfinity: "-inf", + nan: "nan") decoder.keyDecodingStrategy = .convertFromSnakeCase let value = try decoder.decode(Test.self, from: data) XCTAssertEqual(value.value, 100) diff --git a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersVectorsTests.swift b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersVectorsTests.swift index 61dbe50ab..be9b405e0 100644 --- a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersVectorsTests.swift +++ b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersVectorsTests.swift @@ -53,6 +53,26 @@ final class FlatBuffersVectors: XCTestCase { // swiftformat:enable all } + func testCreateStructArray() { + struct Vec: NativeStruct { + let x, y, z: Float32 + } + let vector: [Vec] = [ + Vec(x: 1, y: 2, z: 3), + Vec(x: 4, y: 5, z: 6), + Vec(x: 7, y: 8, z: 9), + ] + var b = FlatBufferBuilder(initialSize: 100) + let o = b.createVector(ofStructs: vector) + b.finish(offset: o) + vector.withUnsafeBytes { pointer in + print(Array(pointer)) + } + // swiftformat:disable all + XCTAssertEqual(b.sizedByteArray, [4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 160, 64, 0, 0, 192, 64, 0, 0, 224, 64, 0, 0, 0, 65, 0, 0, 16, 65]) + // swiftformat:enable all + } + func testCreateEmptyIntArray() { let numbers: [Int32] = [] var b = FlatBufferBuilder(initialSize: 20)