Skip to content

Fixes #9000 - #9014

Open
NalinDalal wants to merge 7 commits into
processing:mainfrom
NalinDalal:main
Open

Fixes #9000#9014
NalinDalal wants to merge 7 commits into
processing:mainfrom
NalinDalal:main

Conversation

@NalinDalal

Copy link
Copy Markdown
Contributor

Resolves #9000

set() on p5.Graphics was significantly slower in 2.x vs 1.11.x because
_getRGBA did a full to('srgb') + structuredClone + map() on every
call, even when the color was already in RGB mode and being reused in a
tight loop.

Changes:

  1. Cache _getRGBA results
    • Added #rgbaCache Map on Color, keyed by maxes string
    • First call computes and stores; subsequent calls with same maxes
      return a shallow copy of the cached array
    • Cache is invalidated in setRed, setGreen, setBlue, setAlpha,
      and the _color setter
  2. RGB-mode fast path
    • When this.mode === RGB, coords are already sRGB 0-1, so skip the
      to('srgb') conversion and use [...this._color.coords, this._color.alpha]
      directly
    • Non-RGB modes still go through colorjs conversion

AI usage disclosure: I used Kilo (an AI coding assistant) to help
implement and review this change. I understand and take responsibility for
every line of code in this PR. See [AI_USAGE_POLICY.md].

PR Checklist

@ksen0

ksen0 commented Jul 24, 2026

Copy link
Copy Markdown
Member

To show that this fixes the original issue, please report results from the linked benchmark test, at minimum. Thanks!

@NalinDalal

Copy link
Copy Markdown
Contributor Author

Confirmed the fix in 88e1550 — benchmark (100×100 buffer, 50 iterations, Brave):

  • Average: 2.41 ms
  • Min: 1.90 ms
  • This matches the v1.11.12 baseline of ~1.85 ms, down from the reported 12.79 ms in 2.3.0.
Screenshot 2026-07-25 at 12 59 39 PM

Hope this resolves your query @ksen0

@perminder-17 perminder-17 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The results are good, just one concern for the cleaner code.

Comment thread src/color/p5.Color.js Outdated
});

return coords;
this.#rgbaCache.set(cacheKey, result);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this Map(). The only heavy step is the 'srgb' conversion you did above, so we can just cache that part, less complexity and cleaner code.

Comment thread src/color/p5.Color.js Outdated
}

_getRGBA(maxes=[1, 1, 1, 1]) {
const cacheKey = maxes.join(',');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxes.join(',') allocates a new string on every call inside loops. Using a fast path key check for [255, 255, 255, 255] avoids string allocations during pixel loops.

Comment thread src/color/p5.Color.js Outdated

coords = coords.map((coord, i) => {
let coords;
if (this.mode === RGB) {

@Ayush4958 Ayush4958 Jul 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use this._color.space.id === 'srgb' instead of this.mode === RGB makes the fast-path work for any color object backed by sRGB. It ensures that any color whose underlying color space is sRGB will bypass colorjs conversion and structuredClone, even if the color mode was initialized via another path.

@NalinDalal

Copy link
Copy Markdown
Contributor Author

All feedback are addressed and solved, thanks for your input @Ayush4958 and @perminder-17

@ksen0 ksen0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, thanks ! And thanks to reviewers!

@perminder-17 perminder-17 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@p5-bot

p5-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Continuous Release

CDN link

Published Packages

Commit hash: 7f4b60a

Previous deployments

This is an automated message.

@limzykenneth limzykenneth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read the changes correctly, is it essentially memoizing the _getRGBA function? The implementation should be ok and functional but having a #invalidateSrgbCache can be a bit brittle in that we need to make sure that #invalidateSrgbCache is called on all possible code paths (including in the future) that changes the color somehow. If it is just some form of memoizing, can we create a memoization whose key is the color coordinates? Similar to what toString() uses ${this._color.space.id}-${this._color.coords.join(',')}-${this._color.alpha}-${format}?

Comment thread test/bench/set_benchmark.html Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file shouldn't be committed as it does not match the required format of the benchmark harness.

@limzykenneth limzykenneth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for now. If you can do a test with benchmarking to see if it affect performance or not that would be great. After that we can merge.

Comment thread src/color/p5.Color.js
Comment on lines +732 to +734
if (srgbCoordsCache.size > 1000) {
srgbCoordsCache.delete(srgbCoordsCache.keys().next().value);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to have this. I wonder if 1000 is the right number? What would a typical number of colors set() would manipulate and what would be a sensible upper bound without it leaking memory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typical sketches use far fewer than 1000 unique colors. However, image processing or complex generative art could exceed this. Each entry is small (~32 bytes), so 1000 entries ≈ 32KB max. I chose 1000 as a conservative default that covers most cases without significant memory impact. Would you prefer a different value or should we make it configurable?

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.

[p5.js 2.0+ Bug Report]: Large slowdown in p5.Graphics.set() compared to v1.11.x

5 participants