Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
57 changes: 57 additions & 0 deletions Benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,60 @@ 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. Both class variants are compiled into the **same build** via per-class `@JS(identityMode: true)` annotations, so no separate builds or config files are needed.

Requires `--expose-gc` for memory benchmarks.

### Build once

```bash
swift package --swift-sdk $SWIFT_SDK_ID js -c release
```

### Compare both modes in one run

```bash
node --expose-gc run.js --identity-mode=both --identity-iterations=1000000
```

### Run only one mode

```bash
# Only pointer (identity-cached) classes
node --expose-gc run.js --identity-mode=pointer --identity-iterations=1000000

# Only non-identity classes
node --expose-gc run.js --identity-mode=none --identity-iterations=1000000
```

### Additional options

```bash
# 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

# Filter to specific identity benchmarks
node --expose-gc run.js --adaptive --filter=passBothWaysRoundtrip --identity-mode=both --identity-iterations=1000000
```

### 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) |
103 changes: 103 additions & 0 deletions Benchmarks/Sources/Benchmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,109 @@ 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: - Identity Mode Benchmark Variants
// These classes use @JS(identityMode: true) so that identity cache benchmarks
// can run in the SAME build alongside the non-identity classes above.

@JS(identityMode: true)
class SimpleClassIdentity {
@JS var name: String
@JS var count: Int
@JS var flag: Bool
@JS var rate: Float
@JS var precise: Double

@JS init(name: String, count: Int, flag: Bool, rate: Float, precise: Double) {
self.name = name
self.count = count
self.flag = flag
self.rate = rate
self.precise = precise
}
}

@JS(identityMode: true)
class ClassRoundtripIdentity {
@JS init() {}

@JS func roundtripSimpleClassIdentity(_ obj: SimpleClassIdentity) -> SimpleClassIdentity {
return obj
}

@JS func makeSimpleClassIdentity() -> SimpleClassIdentity {
return SimpleClassIdentity(name: "Hello", count: 42, flag: true, rate: 0.5, precise: 3.14159)
}

@JS func takeSimpleClassIdentity(_ obj: SimpleClassIdentity) {
// consume without returning
}
}

nonisolated(unsafe) var _cachedPoolIdentity: [SimpleClassIdentity] = []

@JS(identityMode: true)
class IdentityCacheBenchmarkIdentity {
@JS init() {}

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

@JS func getPoolRepeated() -> [SimpleClassIdentity] {
return _cachedPoolIdentity
}
}

// MARK: - Array Performance Tests

@JS struct Point {
Expand Down
Loading
Loading