Speed of set(x, y, color) in p5.js 2.1.2 vs 1.11.11

Hello, I will be teaching a course starting next week (Mathematics and Computer Science). I typically use p5.js to teach it, and will do that again this year. I was thinking about moving up to version 2.1.2 (because it’s new and not because 1.11.11 was causing any problems).

So I wrote a simple test program that colors all the pixels a random color and put it in my draw() function and found that in 2.1.2 the colors cycle slowly enough that I can actually count each frame. But in 1.11.11 the same code runs so much faster (it just looks like random flickering that is impossible to count).

I saw a previous discussion about the performance of point() between 1.x and 2.x, but I understand that set() works very differently.

Is there something about 2.x that I should know about that would explain this speed difference and how to avoid this kind of problem? If a simple function like this is going to cause problems, I would like to know and maybe I’ll just switch back to 1.11.11.

Thank you for your advice!

function setup() {
   createCanvas(400, 400);
}

function draw() {
   let c = color(random(256), random(256), random(256));

   for (let x = 0; x < width; x++) {
      for (let y = 0; y < height; y++) {
         set(x, y, c);
      }
   }
   updatePixels();
}

Color parsing is currently heavier in 2.x due to the use of a new library that lets p5 convert between more color spaces. We’re actively working on optimizing the easiest case of this, where you don’t actually convert at all and just directly use the format you create the color in, but in the mean time, creating a color object will be slower in 2.x.

If you’re setting a bunch of colors in a loop, the fastest thing for you to do is probably to use the pixels array. This means you incur no color creation cost. Especially in your example, where you’re already working in RGB space, this should be relatively straightforward:

function setup() {
  createCanvas(400, 400);
  loadPixels(); // Creates the initial pixels array
}

function draw() {
  let density = pixelDensity();
  for (let x = 0; x < width; x++) {
     for (let y = 0; y < height; y++) {
        pixels[(y * width + x) * density * 4 + 0] = random(256); // red
        pixels[(y * width + x) * density * 4 + 1] = random(256); // green
        pixels[(y * width + x) * density * 4 + 2] = random(256); // blue
        pixels[(y * width + x) * density * 4 + 3] = 255; // alpha
     }
  }
  updatePixels();
}

Faster than both, if you can manage it, could be to use a shader to do the above in parallel:

let randomColors;
function setup() {
  createCanvas(400, 400, WEBGL);
  randomColors = baseFilterShader().modify(() => {
    let t = uniformFloat(() => frameCount)
    getColor((inputs) => {
      return [
        abs(noise(inputs.texCoord.x * 1000, inputs.texCoord.y * 1000, t)),
        abs(noise(inputs.texCoord.x * 1000, inputs.texCoord.y * 1000, t + 100)),
        abs(noise(inputs.texCoord.x * 1000, inputs.texCoord.y * 1000, t + 200)),
        1
      ]
    });
  });
}

function draw() {
  filter(randomColors);
}

Live: Phantom meter by davepagurek -p5.js Web Editor

2 Likes

Thank you! Your first paragraph says it all. I appreciate knowing that it’s a known limitation and that it’s a target for improvement. The difference between versions is significant (60-ish frames per second vs 2-ish) when I switch between 1.x and 2.x. I look forward to seeing how this improves.

And thank you for the advice on accessing the pixels array directly. That’s something I already teach my students. But I like to look at the simpler loop as a first attempt. We also spend time looking at how bit-shifting can yield performance improvements over function calls like red(), green(), blue(), and color().

To be honest, most of this comes up when I teach about image processing. When I get to that point in the course, I typically leave p5.js and use Processing instead because Processing is much faster at the things I do with my students.

Thank you again!