Skip to content

Project Synesthesia effects without allocating per point - #797

Merged
nulltask merged 4 commits into
develfrom
perf/synesthesia-allocations
Oct 27, 2026
Merged

nulltask merged 4 commits into
develfrom
perf/synesthesia-allocations

Conversation

@nulltask

@nulltask nulltask commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • drawPointCloud computes the cloud's scale, yaw/pitch cosines and camera basis once per cloud and projects each point with projectCloudPointInto into a reused result, instead of building four intermediate vectors per point.
  • renderBomb computes the spin's cosine and sine once per bomb and projects its fountain and skirt grains with burstParticlePositionInto / projectYawedInto into reused results.
  • The light rivers (play and select) use particleRiverPointInto and projectViewInto with the frame's camera basis instead of particleRiverPoint → viewPoint → projectPoint with object spreads.
  • drawSchool takes project(out, point) and keeps six scratch results, so the mantas' six projections per fish no longer allocate; both callers project through projectViewInto.
  • The new helpers do the same arithmetic as the composed ones; tests check exact equality against projectPoint(viewPoint(rotateX(rotateY(…)))), burstParticlePosition, projectPoint(rotateY(…)) and particleRiverPoint over random inputs.

Measurements

Node micro-benchmark, same inputs:

Case Before After
Point cloud, 2000 points 41.1 µs 26.9 µs
Bomb, 200 grains 6.2 µs 4.7 µs

Browser, unminified build, dense 8-key stress chart on AUTO with Synesthesia, mean requestAnimationFrame callback time over 10 s (≈1200 frames), three alternating runs:

Build Runs (ms/frame) Mean
devel 2.36 / 2.21 / 2.24 2.27
this PR 2.19 / 2.20 / 2.18 2.19

Test plan

  • pnpm run check
  • pnpm test (7203 passed)

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Exports Benchmark

✅ No significant change · 4 within noise · 98 compared

1135efad → 6e67bd0a · 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)

Changed packages

player-browser: 98 cases compared, median 0.1% faster.

All 98 cases
API Base Head Change Run spread (base / head)
player-browser.applySilentModePlayback 14.1 ns 14.2 ns 0.8% slower ±0.4% / ±1.3%
player-browser.applySystemAudioSessionType 12.9 ns 13.0 ns 0.8% slower ±0.2% / ±1.7%
player-browser.asLoadedBytes 7.94 ns 7.89 ns 0.6% faster ±0.6% / ±2.4%
player-browser.blendCodeToPixi 6.32 ns 6.27 ns 0.9% faster ±0.6% / ±2.6%
player-browser.BrowserChartCollectionStore 2.49 ms 2.50 ms 0.5% slower ±0.4% / ±0.6%
player-browser.buildAudioBus 2.56 µs 2.55 µs 0.2% faster ±0.5% / ±1.1%
player-browser.chartPlayVariantForBeatorajaVariant 6.01 ns 6.02 ns 0.2% slower ±0.8% / ±1.9%
player-browser.ChartPreviewEngine 242 ns 282 ns 16.3% slower · ±23.0% / ±24.4%
player-browser.checkBrowserCompat 155 ns 153 ns 1.3% faster ±2.5% / ±0.9%
player-browser.computeChartFileHashes 54.9 µs 54.9 µs ±0.0% ±2.0% / ±2.6%
player-browser.computeChartFileSHA256 46.9 µs 51.4 µs 9.7% slower · ±2.6% / ±3.7%
player-browser.computeSHA256Hex 44.5 µs 44.0 µs 1.2% faster ±2.2% / ±1.7%
player-browser.createAudioService 6.98 µs 6.80 µs 2.6% faster ±2.0% / ±8.4%
player-browser.createBeatorajaSceneKitCaches 78.1 ns 79.7 ns 2.1% slower ±6.3% / ±6.5%
player-browser.createBeMusicSkinRegistry 1.07 µs 1.09 µs 1.2% slower ±1.9% / ±0.2%
player-browser.createChartHashLookup 143 ns 140 ns 1.6% faster ±0.6% / ±2.1%
player-browser.createCourseChartRecord 1.58 µs 1.57 µs 0.7% faster ±0.4% / ±1.3%
player-browser.createCroppedBeatorajaTexture 6.56 ns 6.76 ns 3.0% slower ±0.3% / ±2.9%
player-browser.createDecideScene 281 µs 249 µs 11.3% faster · ±6.7% / ±8.4%
player-browser.createEagerDropPathPredicate 968 ns 970 ns 0.3% slower ±1.6% / ±0.1%
player-browser.createNewCourseEntry 9.97 ns 9.85 ns 1.2% faster ±2.4% / ±0.6%
player-browser.createNonstopAssets 17.7 µs 17.7 µs 0.1% faster ±0.6% / ±0.4%
player-browser.createPlayScene 160 µs 159 µs 0.8% faster ±2.9% / ±5.2%
player-browser.createRandomCourseEntry 1.47 µs 1.45 µs 1.0% faster ±0.3% / ±4.0%
player-browser.createResultScene 48.1 µs 51.7 µs 7.4% slower ±7.3% / ±1.3%
player-browser.createSelectController 13.5 µs 13.6 µs 0.8% slower ±0.2% / ±0.2%
player-browser.createSelectScene 46.0 µs 47.0 µs 2.3% slower ±1.1% / ±2.9%
player-browser.createSkinFamilyRegistry 196 ns 194 ns 0.8% faster ±0.6% / ±1.3%
player-browser.defaultBrowserWorkerCount 6.06 ns 6.15 ns 1.5% slower ±0.4% / ±1.5%
player-browser.DeferredDroppedFile 2.55 µs 2.57 µs 0.8% slower ±0.7% / ±1.5%
player-browser.describeChartCollection 7.86 ns 7.90 ns 0.5% slower ±0.6% / ±2.3%
player-browser.describeRecordingFormat 6.32 ns 6.48 ns 2.5% slower ±2.0% / ±2.9%
player-browser.drawBrowserRandomCourse 4.86 µs 4.82 µs 1.0% faster ±0.2% / ±0.8%
player-browser.executeBrowserTask 9.27 µs 9.22 µs 0.5% faster ±7.2% / ±4.6%
player-browser.findBeatorajaThemeBGM 3.12 µs 3.11 µs 0.3% faster ±0.5% / ±0.4%
player-browser.flipRectToPixi 5.98 ns 6.15 ns 2.8% slower ±0.4% / ±1.1%
player-browser.getBrowserWorkerCount 6.16 ns 6.20 ns 0.6% slower ±0.5% / ±8.3%
player-browser.getMasterVolume 6.24 ns 6.26 ns 0.3% slower ±1.3% / ±0.5%
player-browser.getSystemAudioSession 7.20 ns 7.27 ns 1.1% slower ±0.3% / ±2.5%
player-browser.isBeatorajaLuaSkinFilePath 41.1 ns 41.0 ns 0.1% faster ±0.7% / ±0.3%
player-browser.isBeatorajaSkinIndicator 136 ns 136 ns 0.1% faster ±0.7% / ±0.2%
player-browser.isChartFilePath 35.5 ns 35.4 ns 0.2% faster ±0.3% / ±0.1%
player-browser.isCourseFilePath 481 ns 482 ns 0.4% slower ±1.2% / ±0.5%
player-browser.isLR2SkinFilePath 41.0 ns 41.4 ns 1.1% slower ±0.1% / ±1.5%
player-browser.loadAssetBytes 243 ns 237 ns 2.3% faster ±2.3% / ±0.9%
player-browser.loadBeatorajaChartImages 568 ns 556 ns 2.1% faster ±1.0% / ±4.4%
player-browser.loadBeatorajaFonts 636 ns 624 ns 1.9% faster ±13.0% / ±1.5%
player-browser.loadBeatorajaSceneKit 16.6 µs 16.6 µs ±0.0% ±0.3% / ±0.7%
player-browser.loadBeatorajaTexturesFromBundle 331 ns 334 ns 1.1% slower ±4.5% / ±0.6%
player-browser.loadBeatorajaThemeFromFiles 594 ns 582 ns 2.0% faster ±3.1% / ±3.9%
player-browser.loadChartCollectionFromDrop 1.22 ms 1.22 ms 0.2% faster ±0.4% / ±0.9%
player-browser.loadChartCollectionFromFiles 1.22 ms 1.21 ms 0.3% faster ±0.2% / ±0.9%
player-browser.logger 11.0 ns 10.9 ns 1.0% faster ±0.2% / ±1.4%
player-browser.masterOutput 13.1 ns 13.1 ns 0.3% faster ±1.1% / ±0.6%
player-browser.materializeDroppedFiles 1.88 µs 1.89 µs 0.5% slower ±0.5% / ±0.7%
player-browser.md5HexOfBytes 744 ns 747 ns 0.4% slower ±0.3% / ±1.9%
player-browser.mergeCompressorParams 110 ns 109 ns 1.4% faster ±2.1% / ±2.3%
player-browser.normalizeBrowserWorkerCount 6.16 ns 6.15 ns 0.1% faster ±0.6% / ±1.6%
player-browser.parseCompressorMode 18.1 ns 18.1 ns 0.1% faster ±0.3% / ±0.2%
player-browser.pickBeatorajaPlayableSkinVariant 20.6 ns 20.6 ns 0.2% slower ±1.2% / ±0.5%
player-browser.pickBeatorajaPlayableVariant 6.27 ns 6.25 ns 0.2% faster ±1.6% / ±2.9%
player-browser.pickRecorderMIMEType 782 ns 776 ns 0.8% faster ±1.0% / ±0.6%
player-browser.readCourseFile 4.67 µs 4.70 µs 0.5% slower ±0.3% / ±0.3%
player-browser.readDroppedFiles 242 ns 244 ns 0.8% slower ±1.8% / ±0.8%
player-browser.readFilesIntoBytesMap 4.37 µs 4.43 µs 1.2% slower (I/O) ±1.6% / ±1.0%
player-browser.recorderMIMECandidates 1.90 µs 1.89 µs 0.1% faster ±0.5% / ±0.7%
player-browser.recordingAudioCodecs 6.23 ns 6.15 ns 1.3% faster ±1.0% / ±2.4%
player-browser.recordingFileExtension 34.8 ns 34.9 ns 0.1% slower ±0.4% / ±0.4%
player-browser.recordingVideoCodecs 52.1 ns 52.1 ns ±0.0% ±2.0% / ±0.8%
player-browser.Rectangle 6.26 ns 6.26 ns ±0.0% ±0.8% / ±1.8%
player-browser.releaseBeatorajaSceneKitAssets 131 ns 132 ns 0.4% slower ±1.5% / ±0.7%
player-browser.resolveBeatorajaChartImage 6.24 ns 6.21 ns 0.5% faster ±0.5% / ±2.7%
player-browser.resolveBeatorajaSkinConfig 130 ns 130 ns 0.3% slower ±0.9% / ±1.3%
player-browser.resolveCaptureSize 6.34 ns 6.29 ns 0.8% faster ±1.2% / ±1.7%
player-browser.resolveChartAsset 2.46 µs 2.47 µs 0.5% slower ±0.8% / ±0.4%
player-browser.resolveChartAudioAsset 10.6 µs 10.6 µs 0.3% slower ±0.7% / ±0.8%
player-browser.resolveChartBGAAsset 49.2 µs 48.8 µs 0.8% faster ±0.2% / ±0.6%
player-browser.resolveChartImageAsset 5.38 µs 5.33 µs 0.9% faster ±0.4% / ±0.9%
player-browser.resolveChartSource 8.77 ns 8.74 ns 0.3% faster ±0.8% / ±2.2%
player-browser.resolveCourseFolders 164 ns 166 ns 1.2% slower ±1.6% / ±1.7%
player-browser.resolveCourseMakerFolders 498 ns 497 ns 0.2% faster ±2.4% / ±1.4%
player-browser.resolveDropFilePath 196 ns 196 ns 0.2% faster ±0.2% / ±0.4%
player-browser.resolveFrameTimestamp 6.24 ns 6.18 ns 1.0% faster ±0.1% / ±1.7%
player-browser.resolveRendererPreference 145 ns 146 ns 0.2% slower ±1.9% / ±2.2%
player-browser.resolveRenderResolution 196 ns 195 ns 0.1% faster ±1.9% / ±1.0%
player-browser.resolveSelectListWindow 11.4 ns 11.3 ns 0.5% faster ±1.0% / ±0.3%
player-browser.resolveSilentModeAudioSessionType 6.15 ns 6.12 ns 0.5% faster ±0.3% / ±2.0%
player-browser.runBrowserTask 1.07 ms 1.07 ms 0.2% faster ±0.1% / ±0.1%
player-browser.sanitizeBusVolume 6.51 ns 6.44 ns 1.2% faster ±0.7% / ±1.7%
player-browser.sanitizeMasterVolume 6.66 ns 6.59 ns 1.0% faster ±1.8% / ±2.0%
player-browser.SessionScene 578 ns 574 ns 0.6% faster ±0.8% / ±1.3%
player-browser.setMasterVolume 403 µs 492 µs 22.1% slower · ±10.6% / ±15.8%
player-browser.shouldCaptureFrame 6.27 ns 6.22 ns 0.7% faster ±0.4% / ±2.0%
player-browser.splitDroppedChartAndThemeFiles 2.59 µs 2.60 µs 0.2% slower ±0.5% / ±0.9%
player-browser.summarizeBeatorajaPlaySkins 122 ns 122 ns 0.2% faster ±0.6% / ±0.6%
player-browser.summarizeBrowserCompat 68.0 ns 67.5 ns 0.6% faster ±12.0% / ±0.7%
player-browser.supportsSystemAudioSession 8.07 ns 8.02 ns 0.7% faster ±0.3% / ±2.0%
player-browser.supportsWebCodecsRecording 11.2 ns 11.1 ns 0.7% faster ±0.5% / ±0.9%
Within noise (4): past the threshold, but the runs overlap
API Base Head Change Run spread (base / head)
player-browser.setMasterVolume 403 µs 492 µs 22.1% slower ±10.6% / ±15.8%
player-browser.ChartPreviewEngine 242 ns 282 ns 16.3% slower ±23.0% / ±24.4%
player-browser.createDecideScene 281 µs 249 µs 11.3% faster ±6.7% / ±8.4%
player-browser.computeChartFileSHA256 46.9 µs 51.4 µs 9.7% slower ±2.6% / ±3.7%
I/O-bound, not judged (1)

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.37 µs 4.43 µs 1.2% slower ±1.6% / ±1.0%
Excluded: too fast to time (10)

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.alignAudioChunk 0.392 ns 0.386 ns
player-browser.applyBeatorajaStretchRect 28.4 ns 26.7 ns
player-browser.CourseMaker 88.0 ns 85.3 ns
player-browser.createBeatorajaSkinLoadContext 181 ns 195 ns
player-browser.defaultBeatorajaSkinConfig 25.7 ns 25.8 ns
player-browser.destinationToSpriteProps 79.1 ns 79.1 ns
player-browser.GameplayRecorder 81.8 ns 79.1 ns
player-browser.resolveCourseEntry 57.5 ns 57.5 ns
player-browser.SceneController 259 ns 259 ns
player-browser.setBrowserWorkerCount 38.9 ns 38.4 ns
Run details
Measure Value
Median change (all compared cases) +0.14%
Mean change -0.25%
Overall verdict (median vs. threshold) unchanged
Compared / within noise / I/O-bound / unchanged 98 / 4 / 1 / 93
Benchmarked / skipped (head) 108 / 6
Time / warmup per case 50 ms / 25 ms
Node / platform v26.12.1 / linux

@nulltask nulltask changed the title Project Synesthesia point clouds and bomb grains without allocating Project Synesthesia effects without allocating per point Oct 26, 2026
@nulltask
nulltask merged commit b2183e0 into devel Oct 27, 2026
9 checks passed
@nulltask
nulltask deleted the perf/synesthesia-allocations 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