Skip to content

Drain the GC's grace pass as it walks, ending the overflow spiral (issue #5537) - #5573

Merged
shai-almog merged 2 commits into
masterfrom
gc-grace-drain-5537
Aug 21, 2026
Merged

Drain the GC's grace pass as it walks, ending the overflow spiral (issue #5537)#5573
shai-almog merged 2 commits into
masterfrom
gc-grace-drain-5537

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

A deep game-tree search that #5563 saved from an EXC_RESOURCE kill came back frozen instead -- GC pauses growing longer and more frequent until they were effectively continuous, with the simulator's footprint climbing to gigabytes while the app retained nothing. Both readings are the same defect, and it sits under the one #5563 fixed rather than beside it.

Root cause

Every cycle the grace pass walks the BiBOP page registry and marks every object allocated since the last cycle -- a fresh object may already be linked into the live graph, so it and its subtree have to survive. How many that is depends on the mutator's allocation rate, not on the live set, and the pass pushed all of them onto a fixed 65536-entry worklist before draining any of it. A worker churning small short-lived objects produces several times that per cycle, so the worklist overflowed as a matter of course.

Overflow is survivable -- the dropped entries are already marked and the belt re-discovers their children -- but the belt is a full O(heap) rescan. It makes the cycle several times longer, the mutator leaves proportionally more fresh objects for the next one, and that one overflows for certain. The collector never returns to its fast path.

Every symptom on the issue follows from that one loop:

symptom mechanism
iPad killed at 1.42GB the spiral, before any ceiling protection existed
app freezes, GC "effectively continuous" #5563's pacing holds the process under the ceiling, so the spiral parks the mutator on nearly every allocation instead
simulator footprint climbing to GBs same spiral where no per-process ceiling exists to bound it

The change

Both grace passes -- the page registry and the legacy table -- drain when the worklist reaches half capacity. That costs nothing the end-of-pass drain would not have cost anyway (the same objects are scanned, only sooner); what it buys is a cursor that cannot run away.

The drain runs outside the trusted window, via a new CN1_GC_TRUSTED_SUSPEND/RESUME pair (BEGIN/END save and restore a block-scoped local and so cannot express a hole inside a walk): a drain follows child words out of arbitrary mark functions, which is precisely what the resolve guard exists for. A _Static_assert pins the remaining assumption -- that a whole page of slots fits above the drain threshold -- so raising CN1_BIBOP_PAGE_SIZE fails the build rather than quietly restoring the spiral.

Measured

Repro of the reporter's shape: worker thread, tree search, live set of one path through the tree.

Realistic version, no ceiling:

master this PR
peak footprint 6.2 GB 231 MB
GC cycle time 6ms -> 750ms flat ~6ms
nodes searched 189M 247M

Heavier version under a 512MB simulated ceiling (the device case):

master this PR
cycles that overflowed 77 of 150 0 of 440
mutator parks (the "frozen" symptom) 72 0
wall clock 10.2s 6.8s
worst-case headroom left 64 MB 174 MB

Test

GcOverflowSpiralIntegrationTest asserts zero overflow cycles under a simulated ceiling, and that the pass actually reached its drain threshold -- otherwise the first assertion would pass on a run that never allocated. Ablating the drain and leaving everything else in place fails it with 77 overflows. Overflow cycles are counted through a new env-gated [GC-OVERFLOW] tracer, taken with an exchange on the existing flag so it reads once per cycle rather than once per dropped push.

Green locally: 519 non-benchmark ParparVM tests, and all 8 benchmark tests including GcHeapIntegrity, LargeArrayGc, BibopPageFloor and ProcessBudgetPacing.

Not addressed here

Off a per-process ceiling the pacing cap is still a fraction of the host's free RAM, so on a RAM-rich Mac a sufficiently extreme allocator can build gigabytes of garbage before anything stalls it. A live-set-relative cap and an absolute cap were both measured and rejected -- each cost 2-4x throughput, because a volume-cap park waits out a whole collection while the footprint-based admission used under a real ceiling is both bounded and free. Extending that admission to hosts that have a footprint probe but no ceiling is the right fix and needs its own benchmarking.

🤖 Generated with Claude Code

…sue #5537)

A deep game-tree search that #5563 saved from an EXC_RESOURCE kill came back
frozen instead: GC pauses growing longer and more frequent until they were
effectively continuous, with the simulator's footprint climbing to gigabytes
while the app retained nothing. Both readings are the same defect, and it sits
under the one #5563 fixed rather than beside it.

Every cycle the grace pass walks the BiBOP page registry and marks every object
allocated since the last cycle -- a fresh object may already be linked into the
live graph, so it and its subtree have to survive. How many that is depends on
the mutator's ALLOCATION RATE, not on the live set, and the pass pushed all of
them onto a fixed 65536-entry worklist before draining any of it. A worker
churning small short-lived objects produces several times that per cycle, so the
worklist overflowed as a matter of course.

Overflow is survivable -- the dropped entries are already marked and the belt
re-discovers their children -- but the belt is a full O(heap) rescan. It makes
the cycle several times longer, the mutator leaves proportionally more fresh
objects for the next one, and that one overflows for certain. The collector never
returns to its fast path. Every symptom on the issue follows from that single
loop: the original kill by the iOS per-process ceiling, the frozen app once
#5563's pacing held the process under that ceiling and had to park the mutator on
nearly every allocation instead, and the simulator's climbing footprint where no
ceiling exists at all.

Both grace passes -- the page registry and the legacy table -- now drain when the
worklist reaches half capacity. That costs nothing the end-of-pass drain would
not have cost anyway, since the same objects are scanned, only sooner; what it
buys is a cursor that cannot run away. The drain runs outside the trusted window
(CN1_GC_TRUSTED_SUSPEND/RESUME, added because BEGIN/END save and restore a
block-scoped local and so cannot express a hole inside a walk): a drain follows
child words out of arbitrary mark functions, which is precisely what the resolve
guard exists for. A _Static_assert pins the remaining assumption -- that a whole
page of slots fits above the drain threshold -- so raising CN1_BIBOP_PAGE_SIZE
fails the build rather than quietly restoring the spiral.

Measured on a repro of the reporter's shape (worker thread, tree search, live set
of one path). Realistic version, no ceiling: peak footprint 6.2GB -> 231MB, cycle
time 6ms->750ms -> a flat 6ms, and 30% more nodes searched. Heavier version under
a 512MB simulated ceiling, which is the device case: 77 of 150 cycles overflowed
and the mutator parked 72 times -> 0 of 440 and no parks, 10.2s -> 6.8s, with
174MB of headroom left instead of 64MB.

GcOverflowSpiralIntegrationTest guards it, asserting zero overflow cycles under a
simulated ceiling and that the pass actually reached its drain threshold (else the
first assertion would pass on a run that never allocated). Ablating the drain and
leaving everything else in place fails it with 77 overflows. Overflow cycles are
counted through a new env-gated [GC-OVERFLOW] tracer, and the count is taken with
an exchange on the existing flag so it reads once per cycle rather than once per
dropped push.

Not addressed here, and separate: off a per-process ceiling the pacing cap is
still a fraction of the HOST's free RAM, so on a RAM-rich Mac a sufficiently
extreme allocator can build gigabytes of garbage before anything stalls it. A
live-set-relative cap and an absolute cap were both measured and rejected -- each
cost 2-4x throughput, because a volume-cap park waits out a whole collection while
the footprint-based admission used under a real ceiling is both bounded and free.
Extending that admission to hosts with a footprint probe but no ceiling is the
right fix and needs its own benchmarking.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog linked an issue Aug 20, 2026 that may be closed by this pull request
3 tasks
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 527 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 18842 ms

  • Hotspots (Top 20 sampled methods):

    • 32.98% java.util.ArrayList.indexOf (556 samples)
    • 3.80% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (64 samples)
    • 3.14% java.lang.StringBuilder.append (53 samples)
    • 3.02% com.codename1.tools.translator.BytecodeMethod.optimize (51 samples)
    • 2.25% org.objectweb.asm.tree.analysis.Analyzer.analyze (38 samples)
    • 2.25% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (38 samples)
    • 2.19% java.lang.System.identityHashCode (37 samples)
    • 2.19% com.codename1.tools.translator.Parser.classIndex (37 samples)
    • 2.08% java.lang.Object.hashCode (35 samples)
    • 1.84% java.lang.String.equals (31 samples)
    • 1.72% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (29 samples)
    • 1.19% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (20 samples)
    • 1.19% java.util.HashMap.hash (20 samples)
    • 1.01% org.objectweb.asm.ClassReader.readCode (17 samples)
    • 0.95% com.codename1.tools.translator.bytecodes.Invoke.addDependencies (16 samples)
    • 0.95% java.lang.StringCoding.encode (16 samples)
    • 0.89% org.objectweb.asm.tree.InsnList.accept (15 samples)
    • 0.89% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (15 samples)
    • 0.89% com.codename1.tools.translator.BytecodeMethod.updateInlinableFieldDependencies (15 samples)
    • 0.83% java.util.HashMap.putVal (14 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 56ms / native 4ms = 14.0x speedup
SIMD float-mul (64K x300) java 64ms / native 4ms = 16.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 197.000 ms
Base64 CN1 decode 130.000 ms
Base64 SIMD encode 93.000 ms
Base64 encode ratio (SIMD/CN1) 0.472x (52.8% faster)
Base64 SIMD decode 97.000 ms
Base64 decode ratio (SIMD/CN1) 0.746x (25.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 36.000 ms
Image createMask (SIMD on) 24.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 58.000 ms
Image applyMask (SIMD on) 52.000 ms
Image applyMask ratio (SIMD on/off) 0.897x (10.3% faster)
Image modifyAlpha (SIMD off) 51.000 ms
Image modifyAlpha (SIMD on) 46.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.902x (9.8% faster)
Image modifyAlpha removeColor (SIMD off) 61.000 ms
Image modifyAlpha removeColor (SIMD on) 54.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.885x (11.5% faster)

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 3ms = 19.6x speedup
SIMD float-mul (64K x300) java 58ms / native 3ms = 19.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 189.000 ms
Base64 CN1 decode 129.000 ms
Base64 SIMD encode 109.000 ms
Base64 encode ratio (SIMD/CN1) 0.577x (42.3% faster)
Base64 SIMD decode 110.000 ms
Base64 decode ratio (SIMD/CN1) 0.853x (14.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 122.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.131x (86.9% faster)
Image applyMask (SIMD off) 48.000 ms
Image applyMask (SIMD on) 35.000 ms
Image applyMask ratio (SIMD on/off) 0.729x (27.1% faster)
Image modifyAlpha (SIMD off) 63.000 ms
Image modifyAlpha (SIMD on) 28.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.444x (55.6% faster)
Image modifyAlpha removeColor (SIMD off) 36.000 ms
Image modifyAlpha removeColor (SIMD on) 27.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.750x (25.0% faster)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 4ms = 13.5x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 245.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.265x (73.5% faster)
Base64 SIMD decode 62.000 ms
Base64 decode ratio (SIMD/CN1) 0.484x (51.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 11.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.727x (27.3% faster)
Image applyMask (SIMD off) 23.000 ms
Image applyMask (SIMD on) 18.000 ms
Image applyMask ratio (SIMD on/off) 0.783x (21.7% faster)
Image modifyAlpha (SIMD off) 16.000 ms
Image modifyAlpha (SIMD on) 10.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.625x (37.5% faster)
Image modifyAlpha removeColor (SIMD off) 20.000 ms
Image modifyAlpha removeColor (SIMD on) 12.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.600x (40.0% faster)

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

The first cut hung the Mac Catalyst screenshot suite: the collector never
finished a cycle, the EDT stacked up in the pacing park behind it, and the
DeviceRunner never reached its completion marker. The hang sample is
unambiguous -- the GC thread sits in gcMarkDrain called from the interleaved
drain this branch added, while the EDT sits in cn1PacingPark under
cn1BibopAlloc.

gcMarkDrain is not "drain the worklist". Every call to it also walks
allObjectsInHeap from index 0 and re-pushes every object already marked this
cycle, so that anything left marked-but-unscanned by an overflow gets its mark
function run. That is the right shape for the handful of calls a cycle makes,
and quadratic for a caller that drains PERIODICALLY: the grace pass drains once
per half-worklist, which turned one O(heap) rescan per cycle into hundreds.

Split the worklist loop out as gcMarkDrainWorklist and point the two interleaved
drains at it. The passes still end with a full gcMarkDrain, which is what closes
the fixpoint; nothing a periodic drain leaves behind escapes it.

The local guard could not see this and now can. A translated micro-benchmark
holds almost nothing in allObjectsInHeap, so an O(table) drain and a cheap one
measure the same -- which is exactly why this passed here and failed on a real
app. GcOverflowSpiralApp now retains a reference-carrying legacy population
(Object[] blocks; the rescan skips objects with no mark function, so an earlier
byte[] version of this fixture was free and proved nothing), and the VM reports
graceFullDrains: full drains taken while a grace pass is running. Two per cycle
is all a correct implementation makes, one to end each pass. Ablating the fix by
pointing the interleaved drain back at gcMarkDrain takes that from 262 across 133
cycles to 1277, and the new assertion fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 318 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 53ms / native 2ms = 26.5x speedup
SIMD float-mul (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 159.000 ms
Base64 CN1 decode 94.000 ms
Base64 native encode 529.000 ms
Base64 encode ratio (CN1/native) 0.301x (69.9% faster)
Base64 native decode 232.000 ms
Base64 decode ratio (CN1/native) 0.405x (59.5% faster)
Base64 SIMD encode 49.000 ms
Base64 encode ratio (SIMD/CN1) 0.308x (69.2% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.479x (52.1% faster)
Base64 encode ratio (SIMD/native) 0.093x (90.7% faster)
Base64 decode ratio (SIMD/native) 0.194x (80.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.143x (85.7% faster)
Image applyMask (SIMD off) 51.000 ms
Image applyMask (SIMD on) 40.000 ms
Image applyMask ratio (SIMD on/off) 0.784x (21.6% faster)
Image modifyAlpha (SIMD off) 43.000 ms
Image modifyAlpha (SIMD on) 39.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.907x (9.3% faster)
Image modifyAlpha removeColor (SIMD off) 47.000 ms
Image modifyAlpha removeColor (SIMD on) 40.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.851x (14.9% faster)

@shai-almog

shai-almog commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1364 seconds

Build and Run Timing

Metric Duration
Simulator Boot 100000 ms
Simulator Boot (Run) 2000 ms
App Install 21000 ms
App Launch 2000 ms
Test Execution 494000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 67ms / native 3ms = 22.3x speedup
SIMD float-mul (64K x300) java 143ms / native 4ms = 35.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 916.000 ms
Base64 CN1 decode 123.000 ms
Base64 native encode 2208.000 ms
Base64 encode ratio (CN1/native) 0.415x (58.5% faster)
Base64 native decode 744.000 ms
Base64 decode ratio (CN1/native) 0.165x (83.5% faster)
Base64 SIMD encode 82.000 ms
Base64 encode ratio (SIMD/CN1) 0.090x (91.0% faster)
Base64 SIMD decode 59.000 ms
Base64 decode ratio (SIMD/CN1) 0.480x (52.0% faster)
Base64 encode ratio (SIMD/native) 0.037x (96.3% faster)
Base64 decode ratio (SIMD/native) 0.079x (92.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.250x (75.0% faster)
Image applyMask (SIMD off) 82.000 ms
Image applyMask (SIMD on) 79.000 ms
Image applyMask ratio (SIMD on/off) 0.963x (3.7% faster)
Image modifyAlpha (SIMD off) 175.000 ms
Image modifyAlpha (SIMD on) 212.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.211x (21.1% slower)
Image modifyAlpha removeColor (SIMD off) 227.000 ms
Image modifyAlpha removeColor (SIMD on) 159.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.700x (30.0% faster)

@shai-almog

shai-almog commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1049 seconds

Build and Run Timing

Metric Duration
Simulator Boot 72000 ms
Simulator Boot (Run) 0 ms
App Install 22000 ms
App Launch 4000 ms
Test Execution 588000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 73ms / native 4ms = 18.2x speedup
SIMD float-mul (64K x300) java 124ms / native 3ms = 41.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 433.000 ms
Base64 CN1 decode 117.000 ms
Base64 native encode 1367.000 ms
Base64 encode ratio (CN1/native) 0.317x (68.3% faster)
Base64 native decode 689.000 ms
Base64 decode ratio (CN1/native) 0.170x (83.0% faster)
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.150x (85.0% faster)
Base64 SIMD decode 70.000 ms
Base64 decode ratio (SIMD/CN1) 0.598x (40.2% faster)
Base64 encode ratio (SIMD/native) 0.048x (95.2% faster)
Base64 decode ratio (SIMD/native) 0.102x (89.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.286x (71.4% faster)
Image applyMask (SIMD off) 107.000 ms
Image applyMask (SIMD on) 302.000 ms
Image applyMask ratio (SIMD on/off) 2.822x (182.2% slower)
Image modifyAlpha (SIMD off) 406.000 ms
Image modifyAlpha (SIMD on) 337.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.830x (17.0% faster)
Image modifyAlpha removeColor (SIMD off) 186.000 ms
Image modifyAlpha removeColor (SIMD on) 255.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.371x (37.1% slower)

@shai-almog
shai-almog merged commit 2b5a004 into master Aug 21, 2026
49 of 51 checks passed
@shai-almog
shai-almog deleted the gc-grace-drain-5537 branch August 21, 2026 04:13
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.

[Bug] ios builds crash

1 participant