EDIT: But it could also be that your computer is somewhat faster than theirs? Do you happen to have some very fast CPU? Can you say which? When I run C-like C++ versions of your code I get the speeds you get with node.js. However, you made overall much better results than they were able, it's still great work!
#include <stdio.h>
int main(int argc, char* argv[]) {
enum { height = 4096, width = 4096 };
unsigned* a = new unsigned[ height*width ];
unsigned* b = new unsigned[ height*width ];
if ( argc < 2 ) { // call with no params
// to measure overhead when just allocations
// and no calculations are done
printf( "%d %d\n", (int)a, (int)b );
return 1;
}
if ( argv[1][0] == '1' ) // call with 1 the fastest
for (unsigned y0 = 0; y0 < height; y0 += 64)
for (unsigned x0 = 0; x0 < width; x0 += 64)
for (unsigned y = y0; y < y0 + 64; y++)
for (unsigned x = x0; x < x0 + 64; x++)
b[x + y*width] = a[y + (width - 1 - x)*height];
else
for (unsigned y = 0; y < height; y++)
for (unsigned x = 0; x < width; x++)
b[x + y*width] = a[y + (width - 1 - x)*height];
return 0;
}https://stackoverflow.com/questions/5200338/a-cache-efficien...
But note that robko made an improvement even before making that.
Or maybe not: my short experiments with the simplified version based on their algorithm and his JavaScript versions gave some conflicting results. I haven't thoroughly verified them, this note is just to motivate the others to try.
OK, I get cca 80ms for my run with the parameter 1 on my main computer, and 200ms on N3150 Celeron.
> b is not used after being calculated
Earlier, I've never seen that any C compiler optimizes away the call to the allocator and the access to the so allocated arrays. Maybe it's different now? Hm, dead code elimination... I guess a random init of the few values before and read and print of a few values after the loop must be always safe... Now that I think, also filling the array with zeroes before.
- Move the "y * width" calculation outside of the "for x" loop.
- The multiply operators can be replaced with addition e.g. replace "y * width" with "counter += width" each y iteration and similarly for the x loop.
Optimising inner loops is really fun.
How much of the speed up in the article is because the JS engine can't figure out how to optimise it compared to the WebAssembly compiler?
I’ll definitely give tiling a spin (although at this point we are definitely fast enough™️)
My guess is that if you try to invoke initial whole code (before tiling) in a external loop (rotating images of exactly the same size), you will get similar perf boost (not that it has practical implication, but just to understand how optimization works).
WASM example would speed up as well using the same approach. Or C, Rust or whatever.
(Stride detecting prefetch can help, especially on the first iteration of a tile, but is not required for a speedup).
BTW this is the motivation for GPUs (and sometimes other graphics applications) using "swizzled" texture/image formats, where pixels are organised into various kinds of screen-locality preserving clumps. https://fgiesen.wordpress.com/2011/01/17/texture-tiling-and-...
Chrome: 248 ms vs 93 ms
Firefox: 552 ms vs 93 ms
MS Edge: 7486 ms vs 6186 ms
IE: 9590 ms vs 9156 ms
These are some WTF results, to be honest.
Seems like a tricky goal for image algorithms in general where you're performing the same action over and over on millions of pixels. Obscure inner loop optimisations are pretty much required.
In these situations, I would sometimes keep the code for the naive but slow version around next to the highly optimised but difficult to understand version. You can compare the output of them to find bugs as well.
Why would non-1 for loop be slower in some browsers? Does the compiler add some sort of prefetch instruction in the faster browsers based on the loop increment?
EDIT: 0.07 seconds with loop tiling: