Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,40 @@ node run.js --filter=Call
node run.js --filter=/^Property access\//
node run.js --filter=/string/i
```

## Identity Mode Benchmarks

Compare `identityMode: "pointer"` vs default (`"none"`) for SwiftHeapObject wrapper caching. Requires `--expose-gc` for memory benchmarks.

```bash
# Run identity benchmarks comparing both modes
node --expose-gc run.js --identity-mode=both

# Pointer mode only
node --expose-gc run.js --identity-mode=pointer

# Control iteration count (default: 1000000)
node --expose-gc run.js --identity-mode=both --identity-iterations=500000

# Control pool sizes for reuse scenarios (default: 1)
node --expose-gc run.js --identity-mode=both --identity-reuse-pools=1,16

# Include memory profiling (heap snapshots before/during/after)
node --expose-gc run.js --identity-mode=both --identity-memory

# Combine with adaptive sampling
node --expose-gc run.js --identity-mode=both --adaptive

# Save identity results
node --expose-gc run.js --identity-mode=both --output=results/identity-mode/results.json
```

### Identity Mode Scenarios

| Scenario | What it measures |
|----------|-----------------|
| `passBothWaysRoundtrip` | Same object crossing boundary repeatedly (cache hit path) |
| `getPoolRepeated_100` | Bulk return of 100 cached objects (model collection pattern) |
| `churnObjects` | Create, roundtrip, release cycle (FinalizationRegistry cleanup pressure) |
| `swiftConsumesSameObject` | JS passes same object to Swift repeatedly |
| `swiftCreatesObject` | Fresh object creation overhead (cache miss path) |
48 changes: 48 additions & 0 deletions Benchmarks/Sources/Benchmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,54 @@ enum ComplexResult {
}
}

// MARK: - Class Array Performance Tests

nonisolated(unsafe) var _classArrayPool: [SimpleClass] = []

@JS class ClassArrayRoundtrip {
@JS init() {}

@JS func setupPool(_ count: Int) {
_classArrayPool = (0..<count).map {
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
}
}

@JS func getPool() -> [SimpleClass] {
return _classArrayPool
}

@JS func makeClassArray() -> [SimpleClass] {
return (0..<100).map {
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
}
}

@JS func takeClassArray(_ values: [SimpleClass]) {}

@JS func roundtripClassArray(_ values: [SimpleClass]) -> [SimpleClass] {
return values
}
}

// MARK: - Identity Cache Benchmark

nonisolated(unsafe) var _cachedPool: [SimpleClass] = []

@JS class IdentityCacheBenchmark {
@JS init() {}

@JS func setupPool(_ count: Int) {
_cachedPool = (0..<count).map {
SimpleClass(name: "Item \($0)", count: $0, flag: true, rate: 0.5, precise: 3.14)
}
}

@JS func getPoolRepeated() -> [SimpleClass] {
return _cachedPool
}
}

// MARK: - Array Performance Tests

@JS struct Point {
Expand Down
158 changes: 158 additions & 0 deletions Benchmarks/Sources/Generated/BridgeJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,164 @@ fileprivate func _bjs_ClassRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPoin
return _bjs_ClassRoundtrip_wrap_extern(pointer)
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_init")
@_cdecl("bjs_ClassArrayRoundtrip_init")
public func _bjs_ClassArrayRoundtrip_init() -> UnsafeMutableRawPointer {
#if arch(wasm32)
let ret = ClassArrayRoundtrip()
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_setupPool")
@_cdecl("bjs_ClassArrayRoundtrip_setupPool")
public func _bjs_ClassArrayRoundtrip_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void {
#if arch(wasm32)
ClassArrayRoundtrip.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count))
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_getPool")
@_cdecl("bjs_ClassArrayRoundtrip_getPool")
public func _bjs_ClassArrayRoundtrip_getPool(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).getPool()
ret.bridgeJSStackPush()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_makeClassArray")
@_cdecl("bjs_ClassArrayRoundtrip_makeClassArray")
public func _bjs_ClassArrayRoundtrip_makeClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).makeClassArray()
ret.bridgeJSStackPush()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_takeClassArray")
@_cdecl("bjs_ClassArrayRoundtrip_takeClassArray")
public func _bjs_ClassArrayRoundtrip_takeClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
ClassArrayRoundtrip.bridgeJSLiftParameter(_self).takeClassArray(_: [SimpleClass].bridgeJSStackPop())
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_roundtripClassArray")
@_cdecl("bjs_ClassArrayRoundtrip_roundtripClassArray")
public func _bjs_ClassArrayRoundtrip_roundtripClassArray(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
let ret = ClassArrayRoundtrip.bridgeJSLiftParameter(_self).roundtripClassArray(_: [SimpleClass].bridgeJSStackPop())
ret.bridgeJSStackPush()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_ClassArrayRoundtrip_deinit")
@_cdecl("bjs_ClassArrayRoundtrip_deinit")
public func _bjs_ClassArrayRoundtrip_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
Unmanaged<ClassArrayRoundtrip>.fromOpaque(pointer).release()
#else
fatalError("Only available on WebAssembly")
#endif
}

extension ClassArrayRoundtrip: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable {
var jsValue: JSValue {
return .object(JSObject(id: UInt32(bitPattern: _bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
consuming func bridgeJSLowerAsProtocolReturn() -> Int32 {
_bjs_ClassArrayRoundtrip_wrap(Unmanaged.passRetained(self).toOpaque())
}
}

#if arch(wasm32)
@_extern(wasm, module: "Benchmarks", name: "bjs_ClassArrayRoundtrip_wrap")
fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
#else
fileprivate func _bjs_ClassArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func _bjs_ClassArrayRoundtrip_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
return _bjs_ClassArrayRoundtrip_wrap_extern(pointer)
}

@_expose(wasm, "bjs_IdentityCacheBenchmark_init")
@_cdecl("bjs_IdentityCacheBenchmark_init")
public func _bjs_IdentityCacheBenchmark_init() -> UnsafeMutableRawPointer {
#if arch(wasm32)
let ret = IdentityCacheBenchmark()
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_IdentityCacheBenchmark_setupPool")
@_cdecl("bjs_IdentityCacheBenchmark_setupPool")
public func _bjs_IdentityCacheBenchmark_setupPool(_ _self: UnsafeMutableRawPointer, _ count: Int32) -> Void {
#if arch(wasm32)
IdentityCacheBenchmark.bridgeJSLiftParameter(_self).setupPool(_: Int.bridgeJSLiftParameter(count))
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_IdentityCacheBenchmark_getPoolRepeated")
@_cdecl("bjs_IdentityCacheBenchmark_getPoolRepeated")
public func _bjs_IdentityCacheBenchmark_getPoolRepeated(_ _self: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
let ret = IdentityCacheBenchmark.bridgeJSLiftParameter(_self).getPoolRepeated()
ret.bridgeJSStackPush()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_IdentityCacheBenchmark_deinit")
@_cdecl("bjs_IdentityCacheBenchmark_deinit")
public func _bjs_IdentityCacheBenchmark_deinit(_ pointer: UnsafeMutableRawPointer) -> Void {
#if arch(wasm32)
Unmanaged<IdentityCacheBenchmark>.fromOpaque(pointer).release()
#else
fatalError("Only available on WebAssembly")
#endif
}

extension IdentityCacheBenchmark: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable {
var jsValue: JSValue {
return .object(JSObject(id: UInt32(bitPattern: _bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
consuming func bridgeJSLowerAsProtocolReturn() -> Int32 {
_bjs_IdentityCacheBenchmark_wrap(Unmanaged.passRetained(self).toOpaque())
}
}

#if arch(wasm32)
@_extern(wasm, module: "Benchmarks", name: "bjs_IdentityCacheBenchmark_wrap")
fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32
#else
fileprivate func _bjs_IdentityCacheBenchmark_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func _bjs_IdentityCacheBenchmark_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 {
return _bjs_IdentityCacheBenchmark_wrap_extern(pointer)
}

@_expose(wasm, "bjs_ArrayRoundtrip_init")
@_cdecl("bjs_ArrayRoundtrip_init")
public func _bjs_ArrayRoundtrip_init() -> UnsafeMutableRawPointer {
Expand Down
Loading
Loading