Skip to content

perf(ios): use immutable Swift wrapper implementation references - #1621

Merged
mrousavy merged 3 commits into
mainfrom
perf/swift-wrapper-immutable-implementation
Sep 7, 2026
Merged

mrousavy merged 3 commits into
mainfrom
perf/swift-wrapper-immutable-implementation

Conversation

@mrousavy

@mrousavy mrousavy commented Sep 7, 2026

Copy link
Copy Markdown
Member

Generated Swift wrappers never replace their implementation after initialization, but store it in a mutable existential. Generate private let __implementation and regenerate the seven Swift wrappers so the compiler can use the wrapper's ownership of that stable reference during forwarding. Getters, setters, and methods can still mutate the referenced HybridObject.

The field changed from let to var in 51a9eb8, alongside the first writable property, when generated protocols could represent value types. HybridObject protocols became class constrained in #176, removing that requirement.

In an isolated Swift 6.3.3 -O -whole-module-optimization probe matching the generated class/protocol composition, let eliminates a retain/release pair around method, getter, and setter forwarding. Protocol witness dispatch remains. A C++ caller against a separately compiled Swift library measured about 2.7–3.0 ns saved per trivial call on an M2 Pro (nine alternating rounds of ten million calls). This measures only the native forwarding path; JS/JSI and actual Nitro application performance have not been measured.

Validation:

  • Full workspace package build and test-package code generation using Node 24.
  • Nitrogen ESLint/Prettier checks and git diff --check.
  • Optimized Swift compile/run probe covering setters, inheritance, class/protocol composition, upcasts, wrapper caching, and reference identity.

@vercel

vercel Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nitro-docs Ready Ready Preview Sep 7, 2026 6:00pm UTC

Request Review

@nitro-modules-bot

Copy link
Copy Markdown

Performance Report

⚠️ Advisory: Results do not fail this PR.

iOS

Benchmark Before After Difference
JavaScript addNumbers() 23.0 ns 24.5 ns 🔴 +6.27% slower
C++ bounce(1 MiB) 223.6 ns 239.6 ns 🔴 +7.16% slower
C++ bounce native-owned buffer (1 MiB) 4.27 µs 3.73 µs 🟢 -12.77% faster
C++ copy(1 MiB) 77.03 µs 86.87 µs 🔴 +12.78% slower
C++ typed map (8 entries) 1.82 µs 1.59 µs 🟢 -12.37% faster
C++ optional trailing string 144.2 ns 134.0 ns 🟢 -7.1% faster
C++ deferred worker Promise (includes trigger call) 22.35 µs 23.55 µs 🔴 +5.39% slower
Swift bounce native-owned buffer (4 KiB) 389.0 ns 362.1 ns 🟢 -6.92% faster
Swift copy(1 MiB) 66.31 µs 74.55 µs 🔴 +12.43% slower
Swift synchronousCallback() 241.8 ns 229.7 ns 🟢 -5% faster
Swift create() 28.80 µs 26.91 µs 🟢 -6.58% faster
Swift deferred worker Promise (includes trigger call) 26.17 µs 27.80 µs 🔴 +6.22% slower
All Benchmarks
Benchmark Before After Difference
C++ bounce(4 KiB) 220.0 ns 212.3 ns 🟢 -3.5% faster
C++ bounce native-owned buffer (4 KiB) 392.1 ns 391.2 ns 🟢 -0.25% faster
C++ copy(4 KiB) 1.07 µs 1.09 µs 🔴 +1.73% slower
C++ large array (1,024) 47.42 µs 47.60 µs 🔴 +0.38% slower
C++ small array (16) 954.8 ns 928.7 ns 🟢 -2.74% faster
C++ synchronousCallback() 202.4 ns 198.8 ns 🟢 -1.75% faster
C++ create() 403.7 ns 395.7 ns 🟢 -1.99% faster
C++ returnExisting() 192.6 ns 194.4 ns 🔴 +0.92% slower
C++ addNumbers() 87.4 ns 87.0 ns 🟢 -0.42% faster
C++ simpleFunc() 66.9 ns 64.9 ns 🟢 -2.92% faster
C++ immediatePromise() 843.1 ns 847.5 ns 🔴 +0.52% slower
C++ number property get/set 127.8 ns 126.5 ns 🟢 -0.95% faster
C++ short ASCII string 142.8 ns 141.0 ns 🟢 -1.2% faster
C++ Unicode string 295.6 ns 281.7 ns 🟢 -4.73% faster
C++ nested Car struct 2.44 µs 2.45 µs 🔴 +0.37% slower
C++ number | string variant 123.7 ns 120.7 ns 🟢 -2.48% faster
Swift bounce(1 MiB) 246.4 ns 250.4 ns 🔴 +1.64% slower
Swift bounce(4 KiB) 235.3 ns 239.0 ns 🔴 +1.56% slower
Swift bounce native-owned buffer (1 MiB) 3.57 µs 3.55 µs 🟢 -0.48% faster
Swift copy(4 KiB) 1.09 µs 1.13 µs 🔴 +4.08% slower
Swift large array (1,024) 48.93 µs 48.11 µs 🟢 -1.66% faster
Swift small array (16) 987.1 ns 974.5 ns 🟢 -1.28% faster
Swift returnExisting() 263.9 ns 254.1 ns 🟢 -3.73% faster
Swift typed map (8 entries) 2.38 µs 2.35 µs 🟢 -1.19% faster
Swift optional trailing string 157.3 ns 158.1 ns 🔴 +0.5% slower
Swift addNumbers() 80.5 ns 82.5 ns 🔴 +2.4% slower
Swift simpleFunc() 64.2 ns 61.7 ns 🟢 -3.94% faster
Swift immediatePromise() 1.32 µs 1.34 µs 🔴 +1.27% slower
Swift number property get/set 144.6 ns 143.0 ns 🟢 -1.13% faster
Swift short ASCII string 156.5 ns 154.9 ns 🟢 -1.05% faster
Swift Unicode string 336.6 ns 339.2 ns 🔴 +0.75% slower
Swift nested Car struct 2.47 µs 2.45 µs 🟢 -1.09% faster
Swift number | string variant 205.6 ns 203.8 ns 🟢 -0.85% faster
TurboModule addNumbers() 921.4 ns 953.7 ns 🔴 +3.51% slower

Android

Benchmark Before After Difference
JavaScript addNumbers() 24.5 ns 23.0 ns 🟢 -5.97% faster
C++ small array (16) 840.1 ns 789.4 ns 🟢 -6.03% faster
C++ create() 610.9 ns 575.4 ns 🟢 -5.81% faster
C++ typed map (8 entries) 1.82 µs 1.92 µs 🔴 +5.43% slower
C++ deferred worker Promise (includes trigger call) 24.78 µs 38.66 µs 🔴 +56% slower
Kotlin deferred worker Promise (includes trigger call) 49.27 µs 32.90 µs 🟢 -33.23% faster
Kotlin immediatePromise() 2.51 µs 2.67 µs 🔴 +6.43% slower
All Benchmarks
Benchmark Before After Difference
C++ bounce(1 MiB) 458.0 ns 467.0 ns 🔴 +1.96% slower
C++ bounce(4 KiB) 463.3 ns 459.1 ns 🟢 -0.91% faster
C++ bounce native-owned buffer (1 MiB) 3.60 µs 3.58 µs 🟢 -0.57% faster
C++ bounce native-owned buffer (4 KiB) 474.8 ns 483.9 ns 🔴 +1.91% slower
C++ copy(1 MiB) 19.95 µs 20.16 µs 🔴 +1.06% slower
C++ copy(4 KiB) 853.3 ns 844.4 ns 🟢 -1.04% faster
C++ large array (1,024) 37.33 µs 37.63 µs 🔴 +0.8% slower
C++ synchronousCallback() 343.6 ns 344.1 ns 🔴 +0.13% slower
C++ returnExisting() 238.3 ns 228.0 ns 🟢 -4.34% faster
C++ optional trailing string 155.3 ns 156.1 ns 🔴 +0.5% slower
C++ addNumbers() 93.7 ns 92.6 ns 🟢 -1.18% faster
C++ simpleFunc() 77.9 ns 77.4 ns 🟢 -0.59% faster
C++ immediatePromise() 910.3 ns 886.3 ns 🟢 -2.63% faster
C++ number property get/set 165.9 ns 173.9 ns 🔴 +4.81% slower
C++ short ASCII string 153.6 ns 147.2 ns 🟢 -4.12% faster
C++ Unicode string 266.3 ns 264.6 ns 🟢 -0.62% faster
C++ nested Car struct 2.26 µs 2.28 µs 🔴 +0.86% slower
C++ number | string variant 139.1 ns 140.4 ns 🔴 +0.99% slower
Kotlin bounce(1 MiB) 1.13 µs 1.15 µs 🔴 +1.22% slower
Kotlin bounce(4 KiB) 1.22 µs 1.16 µs 🟢 -4.84% faster
Kotlin bounce native-owned buffer (1 MiB) 4.31 µs 4.43 µs 🔴 +2.81% slower
Kotlin bounce native-owned buffer (4 KiB) 1.24 µs 1.22 µs 🟢 -1.79% faster
Kotlin copy(1 MiB) 292.46 µs 280.86 µs 🟢 -3.97% faster
Kotlin copy(4 KiB) 5.21 µs 5.19 µs 🟢 -0.28% faster
Kotlin large array (1,024) 43.36 µs 41.50 µs 🟢 -4.3% faster
Kotlin small array (16) 1.06 µs 1.07 µs 🔴 +0.54% slower
Kotlin synchronousCallback() 1.16 µs 1.12 µs 🟢 -3.39% faster
Kotlin create() 1.75 µs 1.79 µs 🔴 +2.17% slower
Kotlin returnExisting() 624.6 ns 612.4 ns 🟢 -1.95% faster
Kotlin typed map (8 entries) 6.62 µs 6.63 µs 🔴 +0.24% slower
Kotlin optional trailing string 355.0 ns 354.0 ns 🟢 -0.27% faster
Kotlin addNumbers() 124.5 ns 124.6 ns 🔴 +0.04% slower
Kotlin simpleFunc() 101.2 ns 101.1 ns 🟢 -0.04% faster
Kotlin number property get/set 241.2 ns 245.3 ns 🔴 +1.7% slower
Kotlin short ASCII string 470.1 ns 467.9 ns 🟢 -0.45% faster
Kotlin Unicode string 779.2 ns 760.2 ns 🟢 -2.43% faster
Kotlin nested Car struct 4.73 µs 4.73 µs 🔴 +0.06% slower
Kotlin number | string variant 428.5 ns 433.5 ns 🔴 +1.17% slower
TurboModule addNumbers() 258.8 ns 246.8 ns 🟢 -4.64% faster

Benchmarking Code Diff 7c7bb7a6...46883b00 (view raw output)

Raw measurements: performance-report-1 (JSON artifact). Run 34149825457, attempt 1. Download requires GitHub access.

Android: measurements, attempt 1, apps, attempt 1.
iOS: measurements, attempt 1, apps, attempt 1.

@mrousavy
mrousavy merged commit ba4f786 into main Sep 7, 2026
20 checks passed

This branch was successfully deployed

1 active deployment
Preview — 46883b00 Deployed Sep 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant