close
Skip to content
Open
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: 27 additions & 10 deletions Benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ This directory contains performance benchmarks for JavaScriptKit.

## Building Benchmarks

Before running the benchmarks, you need to build the test suite:

```bash
swift package --swift-sdk $SWIFT_SDK_ID js -c release
```
Expand All @@ -19,19 +17,38 @@ node run.js
# Save results to a JSON file
node run.js --output=results.json

# Specify number of iterations
node run.js --runs=20

# Run in adaptive mode until results stabilize
node run.js --adaptive --output=stable-results.json

# Run benchmarks and compare with previous results
# Compare with previous results
node run.js --baseline=previous-results.json

# Run only a subset of benchmarks
# Substring match
# Filter benchmarks by name
node run.js --filter=Call
# Regex (with flags)
node run.js --filter=/^Property access\//
node run.js --filter=/string/i
```

## Identity Mode Benchmarks

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.

```bash
# Run only identity benchmarks
node --expose-gc run.js --filter=Identity

# Run only pointer-mode identity benchmarks
node --expose-gc run.js --filter=Identity/pointer

# Run only non-identity baseline
node --expose-gc run.js --filter=Identity/none
```

### Identity 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