Skip to content

Commit d70aaff

Browse files
committed
feat: Add opt-in pointer identity mode for SwiftHeapObject wrappers
1 parent f458fb6 commit d70aaff

55 files changed

Lines changed: 4769 additions & 243 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Benchmarks/README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ This directory contains performance benchmarks for JavaScriptKit.
44

55
## Building Benchmarks
66

7-
Before running the benchmarks, you need to build the test suite:
8-
97
```bash
108
swift package --swift-sdk $SWIFT_SDK_ID js -c release
119
```
@@ -19,19 +17,38 @@ node run.js
1917
# Save results to a JSON file
2018
node run.js --output=results.json
2119

22-
# Specify number of iterations
23-
node run.js --runs=20
24-
2520
# Run in adaptive mode until results stabilize
2621
node run.js --adaptive --output=stable-results.json
2722

28-
# Run benchmarks and compare with previous results
23+
# Compare with previous results
2924
node run.js --baseline=previous-results.json
3025

31-
# Run only a subset of benchmarks
32-
# Substring match
26+
# Filter benchmarks by name
3327
node run.js --filter=Call
34-
# Regex (with flags)
3528
node run.js --filter=/^Property access\//
36-
node run.js --filter=/string/i
3729
```
30+
31+
## Identity Mode Benchmarks
32+
33+
The benchmark suite includes identity-mode variants (`@JS(identityMode: true)`) of the core classes to measure pointer identity caching. Both variants are in the same build and run as regular benchmarks alongside everything else.
34+
35+
```bash
36+
# Run only identity benchmarks
37+
node --expose-gc run.js --filter=Identity
38+
39+
# Run only pointer-mode identity benchmarks
40+
node --expose-gc run.js --filter=Identity/pointer
41+
42+
# Run only non-identity baseline
43+
node --expose-gc run.js --filter=Identity/none
44+
```
45+
46+
### Identity Scenarios
47+
48+
| Scenario | What it measures |
49+
|----------|-----------------|
50+
| `passBothWaysRoundtrip` | Same object crossing boundary repeatedly (cache hit path) |
51+
| `getPoolRepeated_100` | Bulk return of 100 cached objects (model collection pattern) |
52+
| `churnObjects` | Create, roundtrip, release cycle (FinalizationRegistry cleanup pressure) |
53+
| `swiftConsumesSameObject` | JS passes same object to Swift repeatedly |
54+
| `swiftCreatesObject` | Fresh object creation overhead (cache miss path) |

Benchmarks/Sources/Benchmarks.swift

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,109 @@ enum ComplexResult {
257257
}
258258
}
259259

260+
// MARK: - Class Array Performance Tests
261+
262+
nonisolated(unsafe) var _classArrayPool: [SimpleClass] = []
263+
264+
@JS class ClassArrayRoundtrip {
265+
@JS init() {}
266+
267+
@JS func setupPool(_ count: Int) {
268+
_classArrayPool = (0..<count).map {
269+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
270+
}
271+
}
272+
273+
@JS func getPool() -> [SimpleClass] {
274+
return _classArrayPool
275+
}
276+
277+
@JS func makeClassArray() -> [SimpleClass] {
278+
return (0..<100).map {
279+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
280+
}
281+
}
282+
283+
@JS func takeClassArray(_ values: [SimpleClass]) {}
284+
285+
@JS func roundtripClassArray(_ values: [SimpleClass]) -> [SimpleClass] {
286+
return values
287+
}
288+
}
289+
290+
// MARK: - Identity Cache Benchmark
291+
292+
nonisolated(unsafe) var _cachedPool: [SimpleClass] = []
293+
294+
@JS class IdentityCacheBenchmark {
295+
@JS init() {}
296+
297+
@JS func setupPool(_ count: Int) {
298+
_cachedPool = (0..<count).map {
299+
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
300+
}
301+
}
302+
303+
@JS func getPoolRepeated() -> [SimpleClass] {
304+
return _cachedPool
305+
}
306+
}
307+
308+
// MARK: - Identity Mode Benchmark Variants
309+
// These classes use @JS(identityMode: true) so that identity cache benchmarks
310+
// can run in the SAME build alongside the non-identity classes above.
311+
312+
@JS(identityMode: true)
313+
class SimpleClassIdentity {
314+
@JS var name: String
315+
@JS var count: Int
316+
@JS var flag: Bool
317+
@JS var rate: Float
318+
@JS var precise: Double
319+
320+
@JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) {
321+
self.name = name
322+
self.count = count
323+
self.flag = flag
324+
self.rate = rate
325+
self.precise = precise
326+
}
327+
}
328+
329+
@JS(identityMode: true)
330+
class ClassRoundtripIdentity {
331+
@JS init() {}
332+
333+
@JS func roundtripSimpleClassIdentity(_ obj: SimpleClassIdentity) -> SimpleClassIdentity {
334+
return obj
335+
}
336+
337+
@JS func makeSimpleClassIdentity() -> SimpleClassIdentity {
338+
return SimpleClassIdentity(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159)
339+
}
340+
341+
@JS func takeSimpleClassIdentity(_ obj: SimpleClassIdentity) {
342+
// consume without returning
343+
}
344+
}
345+
346+
nonisolated(unsafe) var _cachedPoolIdentity: [SimpleClassIdentity] = []
347+
348+
@JS(identityMode: true)
349+
class IdentityCacheBenchmarkIdentity {
350+
@JS init() {}
351+
352+
@JS func setupPool(_ count: Int) {
353+
_cachedPoolIdentity = (0..<count).map {
354+
SimpleClassIdentity(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
355+
}
356+
}
357+
358+
@JS func getPoolRepeated() -> [SimpleClassIdentity] {
359+
return _cachedPoolIdentity
360+
}
361+
}
362+
260363
// MARK: - Array Performance Tests
261364

262365
@JS struct Point {

0 commit comments

Comments
 (0)