Skip to content

Commit 81edeb1

Browse files
swift 6.0+ performance tweaks 3x-6x (#9067)
* start/stop measurements * tweaks * add 6.0 annotations * remove some inlines * address feedback * address wasi + let * adopt flex buffers * flex buffers benchmarks
1 parent 5982eb6 commit 81edeb1

12 files changed

Lines changed: 288 additions & 27 deletions

File tree

benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ let benchmarks = {
9797
for _ in benchmark.scaledIterations {
9898
blackHole(ByteBuffer(assumingMemoryBound: memory, capacity: Int(oneGB)))
9999
}
100+
benchmark.stopMeasurement()
100101
}
101102

102103
Benchmark("Clearing 1GB", configuration: singleConfiguration) { benchmark in
@@ -105,6 +106,7 @@ let benchmarks = {
105106
for _ in benchmark.scaledIterations {
106107
blackHole(fb.clear())
107108
}
109+
benchmark.stopMeasurement()
108110
}
109111

110112
Benchmark("Strings 10") { benchmark in
@@ -113,6 +115,7 @@ let benchmarks = {
113115
for _ in benchmark.scaledIterations {
114116
blackHole(fb.create(string: str10))
115117
}
118+
benchmark.stopMeasurement()
116119
}
117120

118121
Benchmark("Strings 100") { benchmark in
@@ -121,6 +124,7 @@ let benchmarks = {
121124
for _ in benchmark.scaledIterations {
122125
blackHole(fb.create(string: str100))
123126
}
127+
benchmark.stopMeasurement()
124128
}
125129

126130
Benchmark("Vector 1 Bytes") { benchmark in
@@ -129,6 +133,7 @@ let benchmarks = {
129133
for _ in benchmark.scaledIterations {
130134
blackHole(fb.createVector(bytes: bytes))
131135
}
136+
benchmark.stopMeasurement()
132137
}
133138

134139
Benchmark("Vector 1 Ints") { benchmark in
@@ -137,6 +142,7 @@ let benchmarks = {
137142
for _ in benchmark.scaledIterations {
138143
blackHole(fb.createVector(ints))
139144
}
145+
benchmark.stopMeasurement()
140146
}
141147

142148
Benchmark("Vector 100 Ints") { benchmark in
@@ -145,6 +151,7 @@ let benchmarks = {
145151
for i in benchmark.scaledIterations {
146152
blackHole(fb.createVector(ints))
147153
}
154+
benchmark.stopMeasurement()
148155
}
149156

150157
Benchmark("Vector 100 Bytes") { benchmark in
@@ -153,6 +160,7 @@ let benchmarks = {
153160
for i in benchmark.scaledIterations {
154161
blackHole(fb.createVector(bytes))
155162
}
163+
benchmark.stopMeasurement()
156164
}
157165

158166
Benchmark("Vector 100 ContiguousBytes") { benchmark in
@@ -161,6 +169,7 @@ let benchmarks = {
161169
for i in benchmark.scaledIterations {
162170
blackHole(fb.createVector(bytes: bytes))
163171
}
172+
benchmark.stopMeasurement()
164173
}
165174

166175
Benchmark(
@@ -178,6 +187,7 @@ let benchmarks = {
178187
fb.add(offset: off, at: 8)
179188
blackHole(fb.endTable(at: s))
180189
}
190+
benchmark.stopMeasurement()
181191
}
182192

183193
Benchmark(
@@ -190,6 +200,7 @@ let benchmarks = {
190200
let s = fb.startTable(with: 4)
191201
blackHole(fb.endTable(at: s))
192202
}
203+
benchmark.stopMeasurement()
193204
}
194205

195206
Benchmark("Struct") { benchmark in
@@ -198,6 +209,7 @@ let benchmarks = {
198209
for _ in benchmark.scaledIterations {
199210
blackHole(fb.create(struct: array.first!))
200211
}
212+
benchmark.stopMeasurement()
201213
}
202214

203215
Benchmark("Structs") { benchmark in
@@ -219,6 +231,7 @@ let benchmarks = {
219231
fb.add(offset: vector, at: 4)
220232
let root = Offset(offset: fb.endTable(at: start))
221233
blackHole(fb.finish(offset: root))
234+
benchmark.stopMeasurement()
222235
}
223236

224237
Benchmark("Vector of Offsets") { benchmark in
@@ -239,12 +252,15 @@ let benchmarks = {
239252
fb.add(offset: off, at: 2)
240253
blackHole(fb.endTable(at: s))
241254
}
255+
benchmark.stopMeasurement()
242256
}
243257

244258
Benchmark("Reading Doubles") { benchmark in
245259
let byteBuffer = ByteBuffer(data: data)
260+
benchmark.startMeasurement()
246261
for _ in benchmark.scaledIterations {
247262
blackHole(byteBuffer.read(def: Double.self, position: 0))
248263
}
264+
benchmark.stopMeasurement()
249265
}
250266
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2024 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Benchmark
18+
import FlexBuffers
19+
import Foundation
20+
21+
let benchmarks = {
22+
let data = {
23+
var array = [8888.88, 8888.88]
24+
var data = Data()
25+
array.withUnsafeBytes { ptr in
26+
data.append(contentsOf: ptr)
27+
}
28+
return data
29+
}()
30+
let ints: [Int32] = Array(repeating: 42, count: 100)
31+
let str10 = (0...9).map { _ -> String in "x" }.joined()
32+
let str100 = (0...99).map { _ -> String in "x" }.joined()
33+
34+
// A representative map: 50 keyed scalars, a keyed string and a keyed vector
35+
// of 100 scalars. Used for the realistic decode benchmarks.
36+
let mapBuffer: ByteBuffer = {
37+
var fbx = FlexBuffersWriter(initialSize: 1 << 16)
38+
fbx.map {
39+
for i in 0..<50 { $0.add(int: i, key: "i\(i)") }
40+
$0.add(string: "hello world", key: "s")
41+
$0.vector(key: "v") { v in
42+
for x in 0..<100 { v.add(int: x) }
43+
}
44+
}
45+
fbx.finish()
46+
return fbx.sizedByteBuffer
47+
}()
48+
49+
let metrics: [BenchmarkMetric] = [
50+
.cpuTotal,
51+
.wallClock,
52+
.mallocCountTotal,
53+
.releaseCount,
54+
.peakMemoryResident,
55+
]
56+
let maxIterations = 1_000_000
57+
let maxDuration: Duration = .seconds(3)
58+
let megaConfiguration: Benchmark.Configuration = .init(
59+
metrics: metrics,
60+
warmupIterations: 1,
61+
scalingFactor: .mega,
62+
maxDuration: maxDuration,
63+
maxIterations: maxIterations)
64+
65+
Benchmark.defaultConfiguration = megaConfiguration
66+
67+
// Decode (read path)
68+
69+
// Raw scalar read: isolates `read<T: BitwiseCopyable>` and the `let` blob.
70+
Benchmark("Reading Doubles") { benchmark in
71+
let byteBuffer = ByteBuffer(data: data)
72+
benchmark.startMeasurement()
73+
for _ in benchmark.scaledIterations {
74+
blackHole(byteBuffer.read(def: Double.self, position: 0))
75+
}
76+
benchmark.stopMeasurement()
77+
}
78+
79+
// Realistic decode: resolve root map and read a keyed scalar.
80+
Benchmark("Decode Map Scalar") { benchmark in
81+
benchmark.startMeasurement()
82+
for _ in benchmark.scaledIterations {
83+
let map = try! getRoot(buffer: mapBuffer)!.map!
84+
blackHole(map["i25"]?.int)
85+
}
86+
benchmark.stopMeasurement()
87+
}
88+
89+
// Realistic decode: resolve root map and read a keyed string.
90+
Benchmark("Decode Map String") { benchmark in
91+
benchmark.startMeasurement()
92+
for _ in benchmark.scaledIterations {
93+
let map = try! getRoot(buffer: mapBuffer)!.map!
94+
blackHole(map["s"]?.string())
95+
}
96+
benchmark.stopMeasurement()
97+
}
98+
99+
// Realistic decode: resolve a nested vector and sum its scalars.
100+
Benchmark("Decode Vector") { benchmark in
101+
benchmark.startMeasurement()
102+
for _ in benchmark.scaledIterations {
103+
let map = try! getRoot(buffer: mapBuffer)!.map!
104+
let vector = map["v"]!.vector!
105+
var sum: Int64 = 0
106+
for i in 0..<vector.count {
107+
sum &+= vector[i]?.int ?? 0
108+
}
109+
blackHole(sum)
110+
}
111+
benchmark.stopMeasurement()
112+
}
113+
114+
// Encode (write path)
115+
// Writers are reused with `reset(keepingCapacity:)` so per-iteration
116+
// allocation does not dominate and mask the write-path cost (which is what
117+
// the `@exclusivity(unchecked)` storage pointer affects).
118+
119+
Benchmark("Strings 10") { benchmark in
120+
var fbx = FlexBuffersWriter(initialSize: 1 << 20)
121+
benchmark.startMeasurement()
122+
for _ in benchmark.scaledIterations {
123+
fbx.add(string: str10)
124+
fbx.finish()
125+
blackHole(fbx.sizedByteBuffer)
126+
fbx.reset(keepingCapacity: true)
127+
}
128+
benchmark.stopMeasurement()
129+
}
130+
131+
Benchmark("Strings 100") { benchmark in
132+
var fbx = FlexBuffersWriter(initialSize: 1 << 20)
133+
benchmark.startMeasurement()
134+
for _ in benchmark.scaledIterations {
135+
fbx.add(string: str100)
136+
fbx.finish()
137+
blackHole(fbx.sizedByteBuffer)
138+
fbx.reset(keepingCapacity: true)
139+
}
140+
benchmark.stopMeasurement()
141+
}
142+
143+
Benchmark("Encoding Vector Of Ints") { benchmark in
144+
var fbx = FlexBuffersWriter(initialSize: 1 << 20)
145+
benchmark.startMeasurement()
146+
for _ in benchmark.scaledIterations {
147+
fbx.vector {
148+
$0.create(vector: ints)
149+
}
150+
fbx.finish()
151+
blackHole(fbx.sizedByteBuffer)
152+
fbx.reset(keepingCapacity: true)
153+
}
154+
benchmark.stopMeasurement()
155+
}
156+
157+
Benchmark("Encoding Map") { benchmark in
158+
var fbx = FlexBuffersWriter(initialSize: 1 << 20)
159+
benchmark.startMeasurement()
160+
for _ in benchmark.scaledIterations {
161+
fbx.map {
162+
for i in 0..<50 { $0.add(int: i, key: "i\(i)") }
163+
$0.add(string: "hello world", key: "s")
164+
$0.vector(key: "v") { v in
165+
for x in 0..<100 { v.add(int: x) }
166+
}
167+
}
168+
fbx.finish()
169+
blackHole(fbx.sizedByteBuffer)
170+
fbx.reset(keepingCapacity: true)
171+
}
172+
benchmark.stopMeasurement()
173+
}
174+
}

benchmarks/swift/Package.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,14 @@ let package = Package(
3939
plugins: [
4040
.plugin(name: "BenchmarkPlugin", package: "package-benchmark"),
4141
]),
42+
.executableTarget(
43+
name: "FlexBuffersBenchmarks",
44+
dependencies: [
45+
.product(name: "FlexBuffers", package: "flatbuffers"),
46+
.product(name: "Benchmark", package: "package-benchmark"),
47+
],
48+
path: "Benchmarks/FlexBuffersBenchmarks",
49+
plugins: [
50+
.plugin(name: "BenchmarkPlugin", package: "package-benchmark"),
51+
]),
4252
])

swift/Sources/Common/Scalar.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public let FileIdLength = 4
2828
/// Protocol that All Scalars should conform to
2929
///
3030
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
31-
public protocol Scalar: Equatable {
31+
public protocol Scalar: Equatable, BitwiseCopyable {
3232
associatedtype NumericValue
3333
var convertedEndian: NumericValue { get }
3434
}

0 commit comments

Comments
 (0)