We are about to switch to a new forum software. Until then we have removed the registration on this forum.
I am using the following piece of code to set the color of each pixel:
colorMode(RGB,1);
for (var x = 0; x < nX; x++) {
for (var y = 0; y < nY; y++) {
var id = getIdx(x,y);
var v = vArray[id]; //range=[0,1]
var c = color(v, 0, 1-v);
set(x, y, c);
}
}
updatePixels();
It is very straightforward; i get a value from an array vArray
which is in the range [0,1]
. A color c
is is calculated using color()
and pixel [x,y]
is set to c
.
Unfortunately, this has very bad performance compared to the rest of my code; color()
takes approximately 60% of the CPU time. It is a huge bottleneck compared to the rest of the code which should be the bottleneck considering it is doing heavy work. If i move color()
out of the loop the code runs efficiently again.
Does anyone know why color()
is so inefficient? How can I avoid this inefficiency, either by modifying the use of color()
or perhaps by setting the pixel color in some other way?
Answers
http://forum.processing.org/two/discussion/13074/performance-of-p5-js-when-using-color-and-set
This is part of the long & tortuous path your
color(v, 0, 1-v);
takes: >:)https://GitHub.com/processing/p5.js/blob/master/src/color/creating_reading.js#L244
https://GitHub.com/processing/p5.js/blob/master/src/color/p5.Color.js#L18
https://GitHub.com/processing/p5.js/blob/master/src/color/p5.Color.js#L449
@GoToLoop said:
Ouch! :((
This is slightly painful to see:
Why not:
That's loop optimisation 101 no?
(Edit:) ...or even, since arguments is already an array:
var args = [].slice.call(arguments);
(Edited again - see optimization killers)
To be fair a lot of the rest is helper code to account for more esoteric colour modes. It looks to me as though if you stick to RGB mode and pass separate RGB values it's not quite as bad as it looks.
Still you could try and take a shortcut by creating a p5.Color object directly instead of relying on color():
Thanks for your replies, i posted the same question to SO and there i was given a look at the source for
color()
and it is an unoptimized mess.Fortunately, since i am not interested in 'esoteric' color modes, i was able to improve the code significantly by passing in an RGBA array to
set
rather than calculate aColor
object:for (var x = 0; x < nX; x++) { for (var y = 0; y < nY; y++) { var id = getIdx(x,y); var v = vArray[id]; //range=[0,1] var c = [255*v,0,255*(1-v),255]; //rgba set(x, y, c); } } updatePixels();
no need to set
colorMode()
.Your excerpt still got lots to be improved. I'll try to cook something up here w/
pixels[]
.Just so you know, every single time we call color(), background(),
set(), stroke() and fill(), anew
p5.Color is created unless we pass an already created 1 to them!And you've already witnessed the monstrosity that happens in order to get each p5.Color! :-S
EDIT: set() doesn't create another instance of p5.Color like the others! :bz
Although much more limited to accepted types of color parameters, its parser is much lighter! \m/
I am very interested in any improvements you can suggest. I considered directly setting the
pixels[]
and i know that the documentation states that this can be 'slightly' faster thanset()
but figured it would be micro-optimizing compared to the rest of the code and 'slightly' less readable.Directly instantiating a p5.Color rather than calling color() just cut that useless array instantiation.
However, the most frightful things happen inside "hidden" method _getFormattedColor(). :-O
The only way to guarantee performance is to always pre-instantiate all the p5.Color we're gonna need.
Actually arguments is "array-like" b/c it lacks its actual methods. :-B
Nonetheless there's no need to transform it into a proper Array within color() b/c later on p5.Color's constructor already calls _getFormattedColor() using apply(). :-@
Here's my shorter implementation for color() btW: :ar!
I didn't look properly beyond the first condition since if you pass it arguments of length >= 3 it should complete quickly no?
You're of course correct (and pedantic) as usual :P
You might get away with that for now, but I'd file it as an over-optimisation. From what I can gather it's bad practice to 'leak' the arguments object outside the calling function. What happens if someone comes to p5.Color later and expects a real array?
Also it transpires my suggested use of slice is not good for optimisation: Mozilla docs makes reference to Optimization killers; which explains the use of the seemingly backwards loop; though don't know why the array length isn't cached; presumably as this is now typically done by the JS engine.
Anyway - if the above link is correct it suggests that your attempts to write code as succinctly (and illegibly) as possible don't always result in the best performance :P
Indeed it's a much better case than passing a string. But still it does a lotta division operations.
Function color() is merely an entry-point to create p5.Color objects, not arrays.
And if you examine p5.Color's expected parameter types, you'll see that the 2nd 1 gotta be an arraylike/pseudoarray b/c it passes it to _getFormattedColor() via apply():
this._array = p5.Color._getFormattedColor.apply(pInst, vals);
And apply()'s 2nd argument gotta be an arraylike structure. Otherwise it CRASHES! 8-X
https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
And going back to color(), it commits 2 sins:
1st, It calls
if (args[0] instanceof p5.Color) return args[0];
after:Even though returning the passed p5.Color, which is
arguments[0]
, doesn't need that whole thing!2nd, like I've mentioned, no need to transform arguments into a real array just yet.
p5.Color's constructor already takes care of that! :P
And my color() implementation takes care of both bottlenecks cited above:
Besides accepting any arraylike objects. Not just restricted to "real" arrays. :bz
JavaScript isn't as fast or as smart as the much older & mature Java.
It doesn't have the benefit of a pre-compiled bytecode either.
Nor the certainty of which datatype a variable is holding at a certain moment.
The whole color parser is frequently used by many drawing functions.
So any tiny optimization counts a lot for it! :-\"
Indeed set() is reasonably fast. And a very important reason it's b/c it got a simpler color parser rather than relying on _getFormattedColor() and other colorMode() calculations.
It's always RGBA w/ 0-255 range for each of its 4 channels. B-)
Although not much needed, I said I was cooking something up to be even faster... :^o
Finally I've come to a solution w/
pixels[]
+ Uint32Array, but incompatible w/ "retina" as set() is. :-$Although adding some more complexity, "retina" is easy to support too: :ar!
I appreciate the effort you put into making it faster! can you indicate approximately how much faster this is compared to my solution?
Actually I haven't even run it once. Just blind typing! 8-}
Why don't you try it out and tell us whether you got less CPU or not? O:-)