Skip to content

Performance tuning: PMS playable randomizer and built-in skin particles - #796

Merged
nulltask merged 3 commits into
develfrom
perf/devel-tuning
Oct 26, 2026
Merged

nulltask merged 3 commits into
develfrom
perf/devel-tuning

Conversation

@nulltask

Copy link
Copy Markdown
Owner

Performance pass over devel, measured before changing anything.

How it was measured

  • Exports: the whole export benchmark (pnpm run bench, 918 cases) to rank the slowest calls.
  • Play loop: a 160 s stress chart (≈ 6,700 notes, 8 lanes, BPM changes) on AUTO in a production build, Synesthesia skin. requestAnimationFrame timing, ?perf section timing and Chrome performance traces (unminified build, CPU profile aggregated by function).
  • Library and chart loading: 200 dense charts dropped at once took 322 ms; parsing, resolving and preparing the stress chart take ~2 ms each. Nothing to fix there.

What the play loop spends

Before the change the frame loop used ~25 % of the main thread; the note judging and the shared playfield passes were ~0.1 ms a frame. Nearly all of it was Synesthesia's effects:

  • Pixi rendering (WebGPU command execution and buffer writes);
  • the particle container re-uploading its particles;
  • the skin building its particles each frame.

Changes

  1. PMS RANDOM_PLAYABLE (randomizePMSPlayableLanes), the slowest benchmark case by 4×.
    • It walked all 9! permutations twice and tested each chord's image against ten masks.
    • Every impossible chord has three lanes and a permutation is a bijection, so the search now checks each chord's three-lane subsets against a 512-entry table.
    • It runs as one loop that records valid ordinals, and the pick replays the order to the chosen one.
    • Result: 10.2 ms → 3.1 ms on the benchmark, 24 ms → 9 ms on a dense 600-row chart.
    • The same permutation is picked: an equivalence test keeps the previous search as its reference.
  2. Particles of the built-in skins (pixi-kit/particle-layer.ts).
    • Colors: Pixi's Particle renders from its packed color, and the tint setter parsed a Color for every point every frame. The layer now writes the packed color directly, computed as Pixi computes it (tested against Pixi's own values).
    • Placement: the layer also looked up its index among its siblings twice per point. It now does so once per frame.
    • Result: the frame loop's CPU time −12 % (254 → 223 ms per second); the skin's per-frame build 10.3 % → 7.0 % of the trace.
    • The particles' tint / alpha getters are no longer kept in step; nothing reads them.
  3. ?perf on the built-in play view now prints its per-second section summary. It timed sections but never ended a frame, so samples piled up unreported.

Left as is

  • Pixi's particle upload and WebGPU draw. They are inherent to particles that move every frame; reducing them would change the skin's look.
  • The Debug Menu's multiline monitor. It reads scrollHeight on its interval, about 0.5 %.

Testing

  • Unit:
    • PMS equivalence over random charts and draws, plus the three-lane subset helper.
    • Particle color packing against Pixi's Particle; the colour test now asserts the layer never uses the setters.
  • Checked on screen: Synesthesia's effects (point clouds, floor, sparks, dust) render as before.
  • pnpm run check (0 errors), pnpm test (7199 passed).

🤖 Generated with Claude Code

…ne subsets

Every impossible chord has three lanes and a permutation is a bijection, so a chord's image contains one exactly when one of its three-lane subsets maps onto it: the search checks those subsets against a 512-entry table. It runs as one loop that records the valid permutations' ordinals, and the pick replays the order to the chosen one, so the same permutation is picked (an equivalence test keeps the previous search as the reference).
…cle layers once per frame

Pixi's Particle renders from its packed color alone; the tint setter parsed a Color for every point every frame. The layer also looked up its index among its siblings twice on every point; once per frame is enough.
…ch second

The view timed its sections under ?perf but never ended a frame, so nothing was printed and the samples piled up.
@github-actions

Copy link
Copy Markdown
Contributor

Exports Benchmark

🔴 6 regressions · 3 improvements · 15 within noise · 699 compared

1d4167b3 → 48ffeed4 · 3 runs base / 3 runs head · a regression or improvement is a change past ±8% whose runs do not overlap · times are per call (median)

Regressions (6)

API Base Head Change Run spread (base / head)
player-scene-model.systemSoundSetFromBeatoraja 295 ns 349 ns 18.3% slower ±5.2% / ±5.9%
player-browser.loadBeatorajaThemeFromFiles 517 ns 609 ns 17.9% slower ±8.3% / ±2.6%
player-browser.loadBeatorajaTexturesFromBundle 301 ns 351 ns 16.7% slower ±4.9% / ±2.3%
player-scene-model.playResultFromCourseRun 709 ns 813 ns 14.7% slower ±7.3% / ±2.1%
player-engine.lanes.randomizePMSPlayableNotes 7.71 µs 8.70 µs 12.9% slower ±1.6% / ±2.7%
player-engine.records.mergeCourseStageRecord 22.1 ns 24.5 ns 10.9% slower ±2.1% / ±5.5%

Improvements (3)

API Base Head Change Run spread (base / head)
player-engine.lanes.randomizePMSPlayableLanes 20.9 ms 7.17 ms 65.7% faster ±0.6% / ±0.4%
skin-beatoraja.scene.beatorajaDifficultyOps 18.3 ns 16.6 ns 9.3% faster ±8.8% / ±0.4%
player-engine.course.normalizeCourseTrophies 39.5 ns 36.5 ns 7.8% faster ±3.4% / ±3.5%

Changed packages

player-browser, player-engine: 309 cases compared, median 0.5% faster.

All 309 cases
API Base Head Change Run spread (base / head)
player-browser.alignAudioChunk 6.06 ns 6.31 ns 4.0% slower ±0.3% / ±1.0%
player-browser.applySilentModePlayback 13.8 ns 14.2 ns 2.8% slower ±0.4% / ±0.8%
player-browser.applySystemAudioSessionType 12.6 ns 13.0 ns 3.2% slower ±0.3% / ±0.8%
player-browser.asLoadedBytes 7.54 ns 7.85 ns 4.0% slower ±1.1% / ±0.7%
player-browser.blendCodeToPixi 6.10 ns 6.34 ns 3.9% slower ±1.0% / ±0.2%
player-browser.BrowserChartCollectionStore 2.44 ms 2.45 ms 0.4% slower ±0.1% / ±0.2%
player-browser.buildAudioBus 1.97 µs 4.03 µs 104.5% slower · ±30.4% / ±5.0%
player-browser.chartPlayVariantForBeatorajaVariant 6.27 ns 5.90 ns 6.0% faster ±2.5% / ±1.8%
player-browser.checkBrowserCompat 154 ns 157 ns 1.5% slower ±1.1% / ±2.2%
player-browser.computeChartFileHashes 54.9 µs 55.0 µs 0.3% slower ±1.0% / ±2.6%
player-browser.computeChartFileSHA256 49.3 µs 49.5 µs 0.5% slower ±3.1% / ±1.3%
player-browser.computeSHA256Hex 44.8 µs 45.0 µs 0.5% slower ±1.4% / ±3.9%
player-browser.createAudioService 7.07 µs 6.76 µs 4.3% faster ±6.7% / ±4.2%
player-browser.createBeatorajaSceneKitCaches 80.2 ns 80.2 ns 0.1% faster ±13.8% / ±8.0%
player-browser.createBeatorajaSkinLoadContext 176 ns 175 ns 0.9% faster ±1.2% / ±1.0%
player-browser.createBeMusicSkinRegistry 1.07 µs 1.05 µs 1.8% faster ±0.6% / ±0.6%
player-browser.createChartHashLookup 136 ns 139 ns 1.9% slower ±12.3% / ±1.6%
player-browser.createCourseChartRecord 1.59 µs 1.57 µs 1.3% faster ±3.2% / ±0.3%
player-browser.createCroppedBeatorajaTexture 6.60 ns 6.91 ns 4.7% slower ±0.3% / ±0.4%
player-browser.createDecideScene 251 µs 248 µs 1.1% faster ±4.2% / ±1.8%
player-browser.createEagerDropPathPredicate 1.02 µs 1.00 µs 1.8% faster ±0.4% / ±0.6%
player-browser.createNewCourseEntry 9.83 ns 10.2 ns 4.1% slower ±0.1% / ±0.3%
player-browser.createNonstopAssets 18.7 µs 18.5 µs 1.0% faster ±1.0% / ±0.9%
player-browser.createPlayScene 154 µs 156 µs 0.8% slower ±1.4% / ±0.8%
player-browser.createRandomCourseEntry 1.50 µs 1.53 µs 2.4% slower ±4.6% / ±3.9%
player-browser.createResultScene 58.4 µs 62.2 µs 6.5% slower ±7.8% / ±12.2%
player-browser.createSelectController 14.0 µs 13.9 µs 0.5% faster ±0.6% / ±1.2%
player-browser.createSelectScene 47.8 µs 48.3 µs 1.0% slower ±26.2% / ±2.2%
player-browser.defaultBeatorajaSkinConfig 42.7 ns 41.9 ns 1.9% faster ±0.6% / ±0.5%
player-browser.defaultBrowserWorkerCount 5.99 ns 6.18 ns 3.1% slower ±0.6% / ±0.3%
player-browser.DeferredDroppedFile 2.58 µs 2.54 µs 1.9% faster ±1.6% / ±0.2%
player-browser.describeChartCollection 7.98 ns 8.29 ns 3.9% slower ±0.6% / ±0.5%
player-browser.describeRecordingFormat 6.36 ns 6.66 ns 4.6% slower ±1.8% / ±17.7%
player-browser.drawBrowserRandomCourse 5.06 µs 5.07 µs 0.3% slower ±0.9% / ±4.9%
player-browser.executeBrowserTask 9.22 µs 9.45 µs 2.4% slower ±0.7% / ±2.6%
player-browser.findBeatorajaThemeBGM 2.96 µs 2.99 µs 1.1% slower ±0.8% / ±0.5%
player-browser.flipRectToPixi 6.02 ns 6.21 ns 3.1% slower ±0.8% / ±0.2%
player-browser.getBrowserWorkerCount 6.13 ns 6.42 ns 4.6% slower ±1.1% / ±1.2%
player-browser.getMasterVolume 6.16 ns 6.56 ns 6.5% slower ±1.4% / ±0.8%
player-browser.getSystemAudioSession 7.18 ns 7.57 ns 5.3% slower ±1.9% / ±0.5%
player-browser.isBeatorajaLuaSkinFilePath 41.5 ns 41.0 ns 1.1% faster ±1.4% / ±0.1%
player-browser.isBeatorajaSkinIndicator 136 ns 136 ns ±0.0% ±1.1% / ±0.4%
player-browser.isChartFilePath 36.3 ns 35.4 ns 2.4% faster ±1.1% / ±0.2%
player-browser.isCourseFilePath 476 ns 477 ns 0.1% slower ±0.7% / ±0.5%
player-browser.isLR2SkinFilePath 41.8 ns 41.1 ns 1.6% faster ±1.5% / ±0.4%
player-browser.loadAssetBytes 236 ns 236 ns 0.2% slower ±1.3% / ±1.0%
player-browser.loadBeatorajaChartImages 545 ns 561 ns 2.9% slower ±1.5% / ±0.3%
player-browser.loadBeatorajaFonts 633 ns 642 ns 1.4% slower ±1.8% / ±0.2%
player-browser.loadBeatorajaSceneKit 16.7 µs 16.8 µs 0.5% slower ±1.4% / ±0.1%
player-browser.loadBeatorajaTexturesFromBundle 301 ns 351 ns 16.7% slower 🔴 ±4.9% / ±2.3%
player-browser.loadBeatorajaThemeFromFiles 517 ns 609 ns 17.9% slower 🔴 ±8.3% / ±2.6%
player-browser.loadChartCollectionFromDrop 1.21 ms 1.21 ms 0.2% slower ±0.1% / ±0.3%
player-browser.loadChartCollectionFromFiles 1.21 ms 1.21 ms ±0.0% ±0.3% / ±0.6%
player-browser.logger 11.0 ns 10.8 ns 1.8% faster ±0.3% / ±0.3%
player-browser.masterOutput 13.1 ns 13.1 ns 0.2% faster ±0.3% / ±0.2%
player-browser.materializeDroppedFiles 1.88 µs 1.91 µs 1.8% slower ±0.6% / ±1.8%
player-browser.md5HexOfBytes 739 ns 736 ns 0.5% faster ±0.2% / ±0.2%
player-browser.mergeCompressorParams 108 ns 109 ns 1.0% slower ±2.0% / ±6.7%
player-browser.normalizeBrowserWorkerCount 6.21 ns 6.00 ns 3.4% faster ±0.5% / ±0.7%
player-browser.parseCompressorMode 18.1 ns 18.1 ns 0.4% faster ±0.8% / ±0.1%
player-browser.pickBeatorajaPlayableSkinVariant 20.5 ns 20.3 ns 0.8% faster ±0.4% / ±0.5%
player-browser.pickBeatorajaPlayableVariant 6.31 ns 6.01 ns 4.8% faster ±1.0% / ±0.5%
player-browser.pickRecorderMIMEType 787 ns 792 ns 0.7% slower ±2.5% / ±0.8%
player-browser.readCourseFile 4.58 µs 4.57 µs 0.2% faster ±0.5% / ±0.7%
player-browser.readDroppedFiles 251 ns 255 ns 1.4% slower ±7.5% / ±1.4%
player-browser.readFilesIntoBytesMap 4.56 µs 4.50 µs 1.3% faster (I/O) ±2.6% / ±2.7%
player-browser.recorderMIMECandidates 1.88 µs 1.90 µs 1.2% slower ±2.6% / ±1.3%
player-browser.recordingAudioCodecs 6.19 ns 5.91 ns 4.5% faster ±1.6% / ±1.0%
player-browser.recordingFileExtension 35.4 ns 35.1 ns 0.9% faster ±1.2% / ±0.6%
player-browser.recordingVideoCodecs 51.2 ns 53.7 ns 4.7% slower ±5.1% / ±8.3%
player-browser.Rectangle 6.25 ns 6.00 ns 3.9% faster ±0.8% / ±1.1%
player-browser.releaseBeatorajaSceneKitAssets 118 ns 137 ns 16.1% slower · ±10.2% / ±9.2%
player-browser.resolveBeatorajaChartImage 6.17 ns 5.95 ns 3.5% faster ±0.3% / ±0.6%
player-browser.resolveBeatorajaSkinConfig 131 ns 131 ns 0.2% faster ±5.6% / ±1.7%
player-browser.resolveCaptureSize 6.37 ns 6.05 ns 5.0% faster ±0.9% / ±0.3%
player-browser.resolveChartAsset 2.68 µs 2.66 µs 0.7% faster ±0.4% / ±0.4%
player-browser.resolveChartAudioAsset 11.2 µs 11.1 µs 0.6% faster ±0.6% / ±1.2%
player-browser.resolveChartBGAAsset 51.5 µs 51.3 µs 0.5% faster ±0.4% / ±0.2%
player-browser.resolveChartImageAsset 5.71 µs 5.65 µs 1.0% faster ±0.4% / ±0.8%
player-browser.resolveChartSource 8.77 ns 8.32 ns 5.2% faster ±0.3% / ±0.6%
player-browser.resolveCourseEntry 65.2 ns 64.4 ns 1.2% faster ±0.4% / ±0.8%
player-browser.resolveCourseFolders 185 ns 180 ns 3.0% faster ±5.5% / ±3.2%
player-browser.resolveCourseMakerFolders 551 ns 540 ns 2.0% faster ±9.2% / ±8.7%
player-browser.resolveDropFilePath 211 ns 211 ns 0.1% faster ±0.3% / ±0.2%
player-browser.resolveFrameTimestamp 6.19 ns 6.00 ns 2.9% faster ±0.6% / ±0.4%
player-browser.resolveRendererPreference 147 ns 147 ns 0.1% slower ±0.4% / ±1.1%
player-browser.resolveRenderResolution 194 ns 195 ns 0.5% slower ±0.2% / ±0.9%
player-browser.resolveSelectListWindow 11.4 ns 11.4 ns 0.1% faster ±0.5% / ±0.2%
player-browser.resolveSilentModeAudioSessionType 6.13 ns 5.97 ns 2.6% faster ±1.1% / ±0.3%
player-browser.runBrowserTask 1.08 ms 1.08 ms ±0.0% ±0.0% / ±0.0%
player-browser.sanitizeBusVolume 6.55 ns 6.23 ns 4.8% faster ±0.1% / ±0.4%
player-browser.sanitizeMasterVolume 6.72 ns 6.35 ns 5.5% faster ±0.6% / ±0.5%
player-browser.SessionScene 576 ns 570 ns 1.1% faster ±1.1% / ±0.2%
player-browser.setMasterVolume 503 µs 420 µs 16.5% faster · ±28.4% / ±4.6%
player-browser.shouldCaptureFrame 6.22 ns 6.03 ns 3.1% faster ±0.8% / ±0.7%
player-browser.splitDroppedChartAndThemeFiles 2.79 µs 2.79 µs 0.2% faster ±0.5% / ±0.3%
player-browser.summarizeBeatorajaPlaySkins 123 ns 121 ns 1.0% faster ±2.9% / ±4.1%
player-browser.summarizeBrowserCompat 67.2 ns 67.0 ns 0.3% faster ±0.8% / ±0.3%
player-browser.supportsSystemAudioSession 8.02 ns 7.73 ns 3.7% faster ±0.7% / ±0.4%
player-browser.supportsWebCodecsRecording 11.1 ns 11.0 ns 1.1% faster ±0.4% / ±0.2%
player-engine.course.appendCourseDraftStage 170 ns 171 ns 0.4% slower ±0.8% / ±1.2%
player-engine.course.applyCourseLongNoteConstraint 47.6 ns 47.4 ns 0.4% faster ±2.4% / ±0.2%
player-engine.course.applyUndefinedLongNoteMode 42.1 ns 42.0 ns 0.2% faster ±0.2% / ±1.2%
player-engine.course.beatorajaClearType 6.00 ns 6.22 ns 3.6% slower ±5.1% / ±0.5%
player-engine.course.buildLR2CourseHash 1.61 µs 1.60 µs 0.4% faster ±0.4% / ±0.6%
player-engine.course.buildNonstopCourseReplay 898 µs 880 µs 2.0% faster ±2.7% / ±1.0%
player-engine.course.countLR2UnplayedStages 6.77 ns 7.11 ns 5.0% slower ±0.6% / ±0.2%
player-engine.course.courseConstraintEnumName 48.5 ns 48.9 ns 0.8% slower ±0.6% / ±1.3%
player-engine.course.courseConstraintGroup 6.00 ns 6.18 ns 2.8% slower ±0.9% / ±0.3%
player-engine.course.CourseRun 368 µs 357 µs 2.9% faster ±2.2% / ±1.8%
player-engine.course.createCourseChartScore 89.6 ns 88.6 ns 1.0% faster ±2.4% / ±0.9%
player-engine.course.createCourseDraft 6.01 ns 6.29 ns 4.7% slower ±2.7% / ±1.1%
player-engine.course.createCourseStageRef 140 ns 135 ns 3.6% faster ±2.9% / ±3.0%
player-engine.course.createNonstopStageTiming 2.55 µs 2.55 µs 0.2% slower ±1.4% / ±1.3%
player-engine.course.decodeCourseText 1.18 µs 1.16 µs 1.4% faster ±2.9% / ±2.1%
player-engine.course.defaultCourseMakerConstraints 6.19 ns 5.92 ns 4.4% faster ±1.0% / ±0.7%
player-engine.course.drawRandomCourse 94.1 µs 93.4 µs 0.7% faster ±0.3% / ±0.4%
player-engine.course.evaluateCourseChartFilter 177 µs 177 µs ±0.0% ±1.1% / ±0.4%
player-engine.course.fetchTableHeaderCourses 6.61 µs 6.55 µs 0.8% faster ±0.7% / ±0.5%
player-engine.course.finishCourseDraft 3.80 µs 3.79 µs 0.3% faster ±0.2% / ±0.3%
player-engine.course.isClassCourseConstraints 30.4 ns 30.3 ns 0.3% faster ±0.7% / ±0.3%
player-engine.course.isCourseHiSpeedLocked 8.36 ns 7.97 ns 4.7% faster ±0.9% / ±0.8%
player-engine.course.isCourseStageFailed 78.5 ns 78.5 ns 0.1% faster ±0.2% / ±0.4%
player-engine.course.isLR2GradeCourse 6.53 ns 6.19 ns 5.1% faster ±1.2% / ±0.7%
player-engine.course.nonstopBPMAt 17.9 ns 17.9 ns 0.1% faster ±0.4% / ±0.1%
player-engine.course.normalizeCourseConstraints 227 ns 231 ns 1.5% slower ±0.7% / ±0.3%
player-engine.course.normalizeCourseTrophies 39.5 ns 36.5 ns 7.8% faster 🟢 ±3.4% / ±3.5%
player-engine.course.parseBeatorajaCommandFolderFile 1.69 µs 1.68 µs 0.5% faster ±1.6% / ±1.1%
player-engine.course.parseBeatorajaCourseData 813 ns 813 ns ±0.0% ±0.5% / ±0.2%
player-engine.course.parseBeatorajaCourseFile 3.03 µs 3.04 µs 0.3% slower ±0.4% / ±1.0%
player-engine.course.parseBeatorajaRandomCourseData 107 ns 101 ns 4.8% faster ±3.9% / ±0.9%
player-engine.course.parseCourseChartFilter 8.57 µs 8.49 µs 1.0% faster ±1.2% / ±0.8%
player-engine.course.parseCourseConstraint 27.1 ns 26.9 ns 0.7% faster ±0.1% / ±0.4%
player-engine.course.parseLR2CourseFile 3.76 µs 3.72 µs 1.0% faster ±0.8% / ±0.3%
player-engine.course.parseLR2NonstopConnections 740 ns 749 ns 1.1% slower ±0.2% / ±0.5%
player-engine.course.parseTableHeaderCourses 1.13 µs 1.11 µs 1.5% faster ±1.0% / ±1.1%
player-engine.course.planNonstopCourse 1.07 µs 1.06 µs 1.0% faster ±0.6% / ±1.9%
player-engine.course.prepareNonstopCourse 55.6 µs 55.9 µs 0.4% slower ±0.4% / ±0.8%
player-engine.course.removeLastCourseDraftStage 31.8 ns 31.4 ns 1.3% faster ±0.8% / ±0.1%
player-engine.course.resolveCourseLaneOptions 19.0 ns 19.1 ns 0.5% slower ±0.6% / ±0.5%
player-engine.course.resolveCourseStagePlaySettings 89.1 ns 89.3 ns 0.2% slower ±1.2% / ±0.4%
player-engine.course.resolveCourseStages 27.5 ns 26.6 ns 3.3% faster ±13.4% / ±6.5%
player-engine.course.resolveCourseStageSettings 46.1 ns 46.2 ns ±0.0% ±0.4% / ±0.5%
player-engine.course.resolveGivenUpPlaySeconds 101 ns 99.3 ns 1.3% faster ±0.3% / ±1.6%
player-engine.course.resolveLR2GradeCoursePlaySettings 29.9 ns 29.7 ns 0.9% faster ±1.0% / ±0.5%
player-engine.course.resolveNonstopLastMeasure 6.17 ns 5.92 ns 4.1% faster ±1.2% / ±0.4%
player-engine.course.resolveNonstopRun 21.3 µs 20.7 µs 2.7% faster ±1.9% / ±1.5%
player-engine.course.resolveNonstopStageRanks 389 ns 317 ns 18.5% faster · ±8.1% / ±17.2%
player-engine.course.resolveNonstopTempo 1.28 µs 1.12 µs 12.3% faster · ±4.4% / ±8.6%
player-engine.course.resolveTableHeaderURL 2.29 µs 2.27 µs 0.8% faster ±0.6% / ±0.8%
player-engine.course.resolveTableModeKeys 16.3 ns 16.0 ns 2.4% faster ±1.0% / ±0.2%
player-engine.course.selectCourseCharts 185 µs 184 µs 0.3% faster ±0.9% / ±0.4%
player-engine.course.serializeBeatorajaCourseFile 93.5 ns 93.0 ns 0.6% faster ±0.6% / ±0.2%
player-engine.course.serializeLR2CourseFile 402 ns 406 ns 0.9% slower ±0.3% / ±1.4%
player-engine.course.stepLR2NonstopConnection 14.2 ns 13.9 ns 2.4% faster ±0.3% / ±0.5%
player-engine.course.stitchNonstopCourse 95.8 µs 95.5 µs 0.4% faster ±1.1% / ±1.5%
player-engine.course.withCourseConstraints 183 ns 182 ns 0.4% faster ±0.9% / ±0.2%
player-engine.course.withNonstopConnection 3.95 µs 3.96 µs 0.4% slower ±0.8% / ±0.5%
player-engine.createTempoClock 760 ns 770 ns 1.4% slower ±3.9% / ±1.5%
player-engine.display.adjustBeatorajaJudgeTiming 9.11 ns 8.70 ns 4.5% faster ±14.7% / ±0.9%
player-engine.display.createLR2ConstantScrollMapper 1.26 µs 1.24 µs 1.3% faster ±1.3% / ±1.3%
player-engine.display.createScrollDistanceMapper 1.40 µs 1.41 µs 0.4% slower ±2.3% / ±5.7%
player-engine.display.extractInvisiblePlayableNotes 1.53 µs 1.49 µs 2.6% faster ±2.1% / ±1.6%
player-engine.display.extractLandmineNotes 1.58 µs 1.54 µs 2.5% faster ±1.5% / ±1.8%
player-engine.display.extractPlayableNotes 7.16 µs 7.13 µs 0.4% faster ±0.9% / ±0.2%
player-engine.display.extractTimedNotes 7.03 µs 7.04 µs 0.1% slower ±0.9% / ±0.1%
player-engine.display.formatSeconds 48.6 ns 48.5 ns 0.3% faster ±5.2% / ±0.8%
player-engine.display.isImageResizeAlgorithm 13.6 ns 13.6 ns 0.7% faster ±0.3% / ±0.3%
player-engine.display.isLR2JudgeTimingAutoAdjust 6.18 ns 5.91 ns 4.3% faster ±0.5% / ±0.7%
player-engine.display.pickActiveBGACue 40.9 ns 40.8 ns 0.1% faster ±0.6% / ±0.3%
player-engine.display.pickActiveBGAKey 43.4 ns 43.4 ns 0.1% slower ±0.3% / ±0.2%
player-engine.display.resolveDisplayedDifficultyValue 7.14 ns 6.80 ns 4.7% faster ±1.2% / ±0.7%
player-engine.display.resolveDisplayedJudgeRankLabel 1.38 µs 1.38 µs ±0.0% ±0.9% / ±0.9%
player-engine.display.resolveDisplayedJudgeRankValue 1.42 µs 1.37 µs 3.8% faster ±2.7% / ±0.4%
player-engine.display.resolveDisplayedPlayLevelValue 7.93 ns 7.63 ns 3.8% faster ±1.6% / ±0.9%
player-engine.display.resolveJudgeTimingAutoAdjust 6.15 ns 5.89 ns 4.3% faster ±0.5% / ±0.4%
player-engine.display.ScrollDistanceMapper 1.41 µs 1.37 µs 2.6% faster ±2.8% / ±4.1%
player-engine.display.toTimedInvisibleNote 6.11 µs 6.06 µs 0.7% faster ±0.9% / ±0.5%
player-engine.display.toTimedLandmineNote 6.13 µs 6.07 µs 1.0% faster ±0.5% / ±0.4%
player-engine.display.toTimedPlayableNote 6.12 µs 6.07 µs 0.7% faster ±0.5% / ±0.2%
player-engine.lanes.addLongNotes 20.5 µs 20.5 µs 0.2% slower ±0.8% / ±0.4%
player-engine.lanes.appendFreeZoneInputChannels 2.87 µs 2.88 µs 0.5% slower ±1.8% / ±1.5%
player-engine.lanes.applySevenToNine 24.9 µs 25.2 µs 1.5% slower ±0.6% / ±1.1%
player-engine.lanes.clampExtraNoteDepth 6.13 ns 5.88 ns 4.1% faster ±0.3% / ±0.4%
player-engine.lanes.clampHRandomThresholdBPM 6.27 ns 6.03 ns 3.9% faster ±1.2% / ±0.6%
player-engine.lanes.clampLongNoteRate 6.36 ns 6.08 ns 4.4% faster ±0.8% / ±0.7%
player-engine.lanes.clampScrollRate 6.26 ns 6.00 ns 4.1% faster ±0.5% / ±0.3%
player-engine.lanes.clampScrollSection 6.27 ns 5.97 ns 4.7% faster ±1.3% / ±1.0%
player-engine.lanes.createInputTokenToChannelsMap 567 ns 587 ns 3.5% slower ±0.5% / ±2.7%
player-engine.lanes.createLaneBindings 1.22 µs 1.28 µs 4.6% slower ±2.9% / ±2.1%
player-engine.lanes.drawLaneRandomIndex 6.30 ns 6.05 ns 4.0% faster ±0.1% / ±1.0%
player-engine.lanes.isPlayableInputChannel 7.19 ns 6.84 ns 5.0% faster ±0.7% / ±0.7%
player-engine.lanes.isScratch 6.42 ns 6.12 ns 4.6% faster ±1.4% / ±0.3%
player-engine.lanes.parseLaneRandomSeed 155 ns 155 ns 0.1% faster ±0.1% / ±0.2%
player-engine.lanes.prepareMineModifier 20.6 µs 20.7 µs 0.6% slower ±1.2% / ±0.6%
player-engine.lanes.randomizeAllScratchNotes 9.29 µs 9.36 µs 0.8% slower ±1.0% / ±1.7%
player-engine.lanes.randomizeHRandomNotes 7.99 µs 8.10 µs 1.4% slower ±0.9% / ±1.8%
player-engine.lanes.randomizeLR2ConvergeNotes 2.04 µs 2.05 µs 0.4% slower ±1.4% / ±3.1%
player-engine.lanes.randomizeLR2ScatterNotes 5.19 µs 5.11 µs 1.5% faster ±1.5% / ±1.5%
player-engine.lanes.randomizePMSConvergeNotes 15.1 µs 15.2 µs 1.1% slower ±0.9% / ±1.5%
player-engine.lanes.randomizePMSPlayableLanes 20.9 ms 7.17 ms 65.7% faster 🟢 ±0.6% / ±0.4%
player-engine.lanes.randomizePMSPlayableNotes 7.71 µs 8.70 µs 12.9% slower 🔴 ±1.6% / ±2.7%
player-engine.lanes.randomizeSpiralNotes 1.14 µs 1.15 µs 1.4% slower ±0.4% / ±3.6%
player-engine.lanes.randomizeSRandomNotes 10.1 µs 10.3 µs 2.5% slower ±0.7% / ±13.0%
player-engine.lanes.resolveAutoScratchChannels 81.3 ns 83.3 ns 2.4% slower ±3.3% / ±3.3%
player-engine.lanes.resolveAutoScratchSides 8.57 ns 8.57 ns 0.1% faster ±1.0% / ±3.5%
player-engine.lanes.resolveKeyChannel 43.6 ns 44.2 ns 1.3% slower ±2.9% / ±0.7%
player-engine.lanes.resolveLaneChannels 8.70 µs 8.96 µs 3.0% slower ±0.6% / ±3.4%
player-engine.lanes.resolveLaneDisplayMode 203 ns 215 ns 5.9% slower ±5.1% / ±4.7%
player-engine.lanes.resolveLaneOptionChannels 59.7 ns 59.4 ns 0.6% faster ±0.2% / ±0.3%
player-engine.lanes.resolveLR2LaneIndex 7.17 ns 6.74 ns 6.0% faster ±0.2% / ±1.2%
player-engine.lanes.resolvePlayVariantLaneChannels 9.61 µs 9.74 µs 1.3% slower ±1.1% / ±3.3%
player-engine.lanes.resolveSideKeySlot 6.20 ns 5.96 ns 4.0% faster ±0.8% / ±0.6%
player-engine.lanes.resolveSideRelativeLaneIndex 6.50 ns 6.17 ns 5.1% faster ±0.2% / ±1.1%
player-engine.lanes.resolveWheelLaneDirection 32.4 ns 35.5 ns 9.6% slower · ±4.3% / ±4.6%
player-engine.PlayerInterruptedError 2.10 µs 2.10 µs ±0.0% ±0.6% / ±0.3%
player-engine.preparePlaybackChart 55.4 µs 51.6 µs 6.8% faster ±2.4% / ±6.5%
player-engine.records.abortedRecordPlayFromResult 20.7 ns 20.7 ns 0.3% faster ±0.2% / ±0.0%
player-engine.records.courseRunKeepsRecord 25.6 ns 25.2 ns 1.7% faster ±2.1% / ±0.3%
player-engine.records.courseRunRecordPlay 6.24 ns 6.00 ns 3.9% faster ±0.2% / ±0.8%
player-engine.records.courseStageKeepsRecord 17.5 ns 17.2 ns 1.8% faster ±6.2% / ±7.1%
player-engine.records.courseStageRecordPlayFromResult 8.75 ns 8.25 ns 5.7% faster ±0.8% / ±0.0%
player-engine.records.isCourseRunAtAnotherTempo 15.3 ns 15.1 ns 1.0% faster ±0.6% / ±0.5%
player-engine.records.isCourseRunAutoPlayed 14.8 ns 14.4 ns 2.2% faster ±0.5% / ±0.5%
player-engine.records.mergeBestRecord 19.9 ns 19.6 ns 1.7% faster ±0.2% / ±0.7%
player-engine.records.mergeCourseStageRecord 22.1 ns 24.5 ns 10.9% slower 🔴 ±2.1% / ±5.5%
player-engine.records.playKeepsRecord 6.18 ns 5.94 ns 3.9% faster ±0.1% / ±0.6%
player-engine.records.recordPlayFromResult 8.31 ns 7.92 ns 4.7% faster ±0.0% / ±0.8%
player-engine.records.recordPlayFromSummary 77.8 ns 77.7 ns 0.1% faster ±0.3% / ±0.4%
player-engine.replay.aggregateCourseResult 533 ns 530 ns 0.4% faster ±0.3% / ±0.4%
player-engine.replay.checkCourseReplayEligibility 5.32 µs 5.30 µs 0.3% faster ±1.3% / ±2.3%
player-engine.replay.checkReplayEligibility 9.71 ns 9.23 ns 5.0% faster ±0.7% / ±0.5%
player-engine.replay.computeCourseFingerprint 4.55 µs 4.52 µs 0.6% faster ±3.6% / ±3.9%
player-engine.replay.computeCourseReplayFingerprint 4.99 µs 4.65 µs 6.8% faster ±5.2% / ±4.9%
player-engine.replay.createCourseFingerprintDocument 437 ns 437 ns ±0.0% ±1.4% / ±0.9%
player-engine.replay.createCourseReplay 128 µs 128 µs 0.1% slower ±0.2% / ±0.3%
player-engine.replay.createReplayRecorder 18.1 µs 18.5 µs 2.5% slower ±4.5% / ±0.5%
player-engine.replay.createReplayViewCoalescer 304 ns 304 ns ±0.0% ±1.2% / ±3.1%
player-engine.replay.diffReplayOptions 370 ns 370 ns 0.1% faster ±0.8% / ±0.2%
player-engine.replay.diffReplayView 76.2 ns 76.3 ns 0.1% slower ±1.5% / ±0.3%
player-engine.replay.extractCourseStage 163 µs 163 µs 0.1% slower ±0.0% / ±0.1%
player-engine.replay.listCourseReplayStageCharts 107 ns 106 ns 0.7% faster ±2.5% / ±2.5%
player-engine.replay.matchesCourseReplayStageChart 65.6 ns 65.3 ns 0.5% faster ±1.3% / ±1.1%
player-engine.replay.parseCourseReplay 134 µs 134 µs 0.5% slower ±0.3% / ±0.3%
player-engine.replay.parseReplay 65.1 µs 65.3 µs 0.2% slower ±0.2% / ±0.5%
player-engine.replay.parseReplayOptionChanges 224 ns 221 ns 1.1% faster ±0.5% / ±0.3%
player-engine.replay.parseReplayOptionState 384 ns 385 ns 0.2% slower ±0.5% / ±0.2%
player-engine.replay.parseReplayTempoChanges 43.9 ns 43.7 ns 0.6% faster ±0.8% / ±1.7%
player-engine.replay.parseReplayViewState 56.8 ns 55.9 ns 1.6% faster ±1.4% / ±0.1%
player-engine.replay.playedAtAnotherTempo 8.32 ns 7.97 ns 4.1% faster ±0.2% / ±0.4%
player-engine.replay.ReplayParseError 2.10 µs 2.10 µs 0.4% faster ±4.7% / ±0.6%
player-engine.replay.resolve____GaugeUnit 6.22 ns 6.00 ns 3.6% faster ±0.2% / ±0.4%
player-engine.replay.resolveBeatorajaDefaultTotal 6.30 ns 6.02 ns 4.4% faster ±0.6% / ±1.4%
player-engine.replay.resolveBeatorajaHardRecoverMultiplier 6.29 ns 6.00 ns 4.6% faster ±1.1% / ±0.4%
player-engine.replay.resolveChartRulesetFacts 26.1 µs 26.0 µs 0.5% faster ±1.8% / ±1.3%
player-engine.replay.resolveCourseLamp 53.7 ns 53.9 ns 0.5% slower ±1.1% / ±1.6%
player-engine.replay.resolveCoursePlayedRuleset 270 ns 269 ns 0.3% faster ±0.4% / ±0.0%
player-engine.replay.resolveCourseReplayFilename 163 ns 158 ns 2.9% faster ±1.6% / ±1.1%
player-engine.replay.resolveCourseStageChartFingerprints 18.7 ns 18.7 ns 0.3% faster ±1.0% / ±0.1%
player-engine.replay.resolveCourseTrophy 8.63 ns 8.27 ns 4.2% faster ±0.5% / ±0.9%
player-engine.replay.resolveLR2DefaultTotal 6.25 ns 6.05 ns 3.2% faster ±1.6% / ±1.4%
player-engine.replay.resolveLR2HardDamageMultiplier 6.30 ns 6.15 ns 2.5% faster ±0.8% / ±0.4%
player-engine.replay.resolveNonstopNoteStages 20.6 µs 20.8 µs 0.9% slower ±1.9% / ±3.0%
player-engine.replay.resolveNonstopReplayStages 482 ns 474 ns 1.5% faster ±1.1% / ±1.3%
player-engine.replay.resolvePlayChart 5.08 µs 5.10 µs 0.3% slower ±0.6% / ±0.8%
player-engine.replay.resolveReplayFilename 983 ns 973 ns 1.0% faster ±0.2% / ±1.1%
player-engine.replay.resolveReplayHiSpeed 18.5 ns 18.2 ns 1.7% faster ±0.5% / ±1.0%
player-engine.replay.resolveReplayOptionsAt 43.0 ns 42.8 ns 0.5% faster ±0.2% / ±0.2%
player-engine.replay.resolveReplayTempoAt 9.02 ns 8.54 ns 5.2% faster ±0.0% / ±0.9%
player-engine.replay.resolveReplayViewAt 133 ns 134 ns 0.7% slower ±0.3% / ±0.6%
player-engine.replay.resolveRulesetConfig 8.37 µs 8.29 µs 1.0% faster ±2.2% / ±3.1%
player-engine.replay.resolveRulesetResultLamp 40.7 ns 46.1 ns 13.4% slower · ±6.2% / ±7.5%
player-engine.replay.resolveRulesetResultMissCount 8.20 ns 7.90 ns 3.6% faster ±0.3% / ±0.5%
player-engine.replay.roundReplayView 40.0 ns 39.9 ns 0.2% faster ±0.1% / ±0.1%
player-engine.replay.serializeCourseReplay 45.3 µs 44.9 µs 0.9% faster ±1.0% / ±0.5%
player-engine.replay.serializeReplay 22.1 µs 22.1 µs 0.3% faster ±0.7% / ±0.2%
player-engine.replay.simulateCourseReplay 276 µs 274 µs 0.7% faster ±1.4% / ±1.0%
player-engine.replay.simulateReplay 49.6 µs 49.1 µs 1.1% faster ±0.8% / ±1.2%
player-engine.replay.simulateReplayRulesets 154 µs 155 µs 0.7% slower ±0.8% / ±7.0%
player-engine.replay.splitNonstopReplay 40.4 µs 40.0 µs 1.1% faster ±0.9% / ±2.5%
player-engine.resolveChartRulesetFacts 22.4 µs 22.1 µs 1.1% faster ±0.6% / ±1.3%
player-engine.resolvePlayChart 4.78 µs 4.79 µs 0.4% slower ±1.3% / ±0.9%
player-engine.ruleset.betterClearLamp 22.4 ns 22.1 ns 1.1% faster ±0.2% / ±0.0%
player-engine.ruleset.clampCustomJudgeRate 6.24 ns 6.02 ns 3.6% faster ±0.6% / ±0.6%
player-engine.ruleset.clearLampAtRank 7.16 ns 6.74 ns 5.8% faster ±1.0% / ±0.3%
player-engine.ruleset.clearLampRank 14.5 ns 14.3 ns 1.4% faster ±0.5% / ±0.3%
player-engine.ruleset.computeScoreRate 7.05 ns 6.62 ns 6.1% faster ±0.3% / ±0.4%
player-engine.ruleset.createEmptyScore 6.18 ns 6.02 ns 2.5% faster ±0.5% / ±0.6%
player-engine.ruleset.isClearLamp 14.0 ns 13.7 ns 2.0% faster ±2.4% / ±0.5%
player-engine.ruleset.isCustomJudgeAssist 116 ns 116 ns 0.1% slower ±0.1% / ±2.2%
player-engine.ruleset.isFullCombo 6.23 ns 5.95 ns 4.4% faster ±0.3% / ±0.6%
player-engine.ruleset.isGaugeType 10.4 ns 9.97 ns 4.0% faster ±0.2% / ±1.6%
player-engine.ruleset.isRulesetId 14.1 ns 14.1 ns 0.1% slower ±0.5% / ±0.3%
player-engine.ruleset.resolve____RankIndexFromScore 7.78 ns 7.55 ns 2.9% faster ±0.1% / ±0.6%
player-engine.ruleset.resolve____RankLabel 10.2 ns 9.93 ns 2.6% faster ±0.4% / ±0.7%
player-engine.ruleset.resolveClearLamp 53.0 ns 53.0 ns ±0.0% ±0.1% / ±0.2%
player-engine.ruleset.resolveCourseClearLamp 27.7 ns 27.5 ns 0.7% faster ±1.1% / ±1.0%
player-engine.ruleset.resolveDisplayedPoor 6.22 ns 5.92 ns 4.8% faster ±0.5% / ±0.3%
player-engine.ruleset.resolveGaugeEndOnFailure 21.1 ns 21.2 ns 0.2% slower ±0.5% / ±0.3%
player-engine.ruleset.resolveGaugeTypes 6.20 ns 5.95 ns 4.0% faster ±0.9% / ±0.5%
player-engine.ruleset.resolveJudgeWindowsMs 25.8 ns 25.6 ns 0.9% faster ±0.2% / ±0.3%
player-engine.ruleset.resolveLR2MoneyScore 6.98 ns 6.57 ns 5.8% faster ±1.8% / ±0.3%
player-engine.ruleset.resolveMissCount 8.81 ns 8.72 ns 1.0% faster ±0.3% / ±2.0%
player-engine.ruleset.topClearLamp 6.15 ns 5.88 ns 4.4% faster ±0.2% / ±0.4%
player-engine.signals.applyHighSpeedControlAction 8.86 ns 8.47 ns 4.4% faster ±0.3% / ±0.3%
player-engine.signals.createPlayerInputSignalBus 102 ns 105 ns 3.2% slower ±4.5% / ±2.8%
player-engine.signals.createPlayerUISignalBus 161 ns 164 ns 2.0% slower ±1.3% / ±4.2%
player-engine.signals.resolveHighSpeedControlActionFromLaneChannels 14.2 ns 13.7 ns 3.6% faster ±0.7% / ±0.8%
player-engine.signals.resolveHighSpeedMultiplier 7.97 ns 7.67 ns 3.8% faster ±0.3% / ±0.5%
Within noise (15): past the threshold, but the runs overlap
API Base Head Change Run spread (base / head)
player-browser.buildAudioBus 1.97 µs 4.03 µs 104.5% slower ±30.4% / ±5.0%
player-terminal.beginWin32InputModeOptIn 21.2 ns 30.4 ns 43.7% slower ±13.9% / ±27.5%
player-terminal.beginStatefulKeyboardProtocolOptIn 36.1 ns 51.3 ns 41.9% slower ±13.5% / ±31.7%
player-scene-model.joinCourseStageHistories 9.30 µs 12.1 µs 30.2% slower ±15.3% / ±37.5%
player-engine.course.resolveNonstopStageRanks 389 ns 317 ns 18.5% faster ±8.1% / ±17.2%
player-browser.setMasterVolume 503 µs 420 µs 16.5% faster ±28.4% / ±4.6%
player-terminal.beginKittyKeyboardProtocolOptIn 26.6 ns 31.9 ns 20.0% slower ±6.0% / ±11.6%
player-node-audio.audio-sink.createBrowserAudioSink 2.98 µs 3.53 µs 18.5% slower ±18.4% / ±21.2%
player-scene-model.resolveRandomModes 34.7 ns 40.6 ns 17.0% slower ±7.8% / ±20.6%
player-engine.course.resolveNonstopTempo 1.28 µs 1.12 µs 12.3% faster ±4.4% / ±8.6%
player-browser.releaseBeatorajaSceneKitAssets 118 ns 137 ns 16.1% slower ±10.2% / ±9.2%
player-engine.replay.resolveRulesetResultLamp 40.7 ns 46.1 ns 13.4% slower ±6.2% / ±7.5%

…and 3 more.

I/O-bound, not judged (6)

These cases touch the filesystem, so their time follows the OS cache and the disk more than the code.

API Base Head Change Run spread (base / head)
player-browser.readFilesIntoBytesMap 4.56 µs 4.50 µs 1.3% faster ±2.6% / ±2.7%
player-terminal.createBgaANSIRenderer 2.44 µs 2.47 µs 1.3% slower ±3.1% / ±2.1%
player-terminal.loadStageFileANSIImage 100 ns 100 ns 0.2% faster ±0.5% / ±0.4%
player-terminal.loadStageFileANSILines 164 ns 169 ns 2.8% slower ±7.5% / ±3.0%
player-terminal.loadTerminalANSIImage 1.08 ms 1.09 ms 0.3% slower ±1.2% / ±4.3%
skin-beatoraja.readFilesIntoBytesMap 19.9 µs 18.8 µs 5.9% faster ±3.6% / ±0.9%
Excluded: too fast to time (61)

A timed sample lasted 1 µs or less on at least one side (usually a base commit from before calls were batched), so its time is timer noise and no change is shown.

API Base Head
player-browser.applyBeatorajaStretchRect 27.3 ns 28.7 ns
player-browser.ChartPreviewEngine 238 ns 245 ns
player-browser.CourseMaker 98.0 ns 100 ns
player-browser.createSkinFamilyRegistry 195 ns 202 ns
player-browser.destinationToSpriteProps 86.3 ns 86.4 ns
player-browser.GameplayRecorder 79.2 ns 82.4 ns
player-browser.SceneController 251 ns 256 ns
player-browser.setBrowserWorkerCount 38.4 ns 38.4 ns
player-engine.display.resolveAltModifierLabel 6.77 ns 6.24 ns
player-engine.lanes.createBeatorajaLaneRandom 384 ns 368 ns
player-engine.records.courseStageRecordPlayFromSummary 87.2 ns 85.3 ns
player-engine.replay.parseReplayViewChanges 227 ns 227 ns

…and 49 more.

Run details
Measure Value
Median change (all compared cases) -0.00%
Mean change -0.09%
Overall verdict (median vs. threshold) unchanged
Compared / within noise / I/O-bound / unchanged 699 / 15 / 6 / 669
Benchmarked / skipped (head) 760 / 35
Time / warmup per case 50 ms / 25 ms
Node / platform v26.12.1 / linux

@nulltask
nulltask merged commit 1135efa into devel Oct 26, 2026
9 checks passed
@nulltask
nulltask deleted the perf/devel-tuning branch October 27, 2026 09:29
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