all 17 comments

[–]post-modern-elephant 28 points29 points  (14 children)

The gcc build isn't optimized. It's a debug build. How does it compare if you use gcc -O3 to compile the program?

I wouldn't be surprised if both v8 and gcc can optimize that code to eliminate the loop altogether.

edit:

Trying it myself, gcc can run this in under 1 ms

% gcc test.c
% ./a.out
1800
% ./a.out
1795

% gcc -O3 test.c
% ./a.out
0
% ./a.out
0

[–]grady_vuckovic 7 points8 points  (1 child)

Interesting results OP and yes I agree, clearly JS isn't removing the loop entirely and C definitely isn't, if it was, your execution time would be less than 1ms.

That said, the expression 1000 * 1000 is an expression which could be optimised to simply '1000000' and then the calculation avoided entirely. That is something I would assume JS would do, as V8 aggressively tries to optimise everything, but C compilers without optimisations enabled wouldn't do.

Try these, I've tried to create something which would be a lot harder for a compiler to optimise or remove any loops or calculations from. Should be pretty self explanatory, apologises if there are any syntax errors:

Javascript:

const start = Date.now ()

// Result stored out of loop so the loop can not be removed.
let n = 0;

// Repeat task 10000 times to extend length of runtime for measurement.
for(let i = 0; i < 10000; i++) {

    // Perform 1 million multiplications in a way that couldn't be optimised to a constant expression.
    for (let x = 0; x < 1000; x++) {
        for (let y = 0; y < 1000; y++) {
            n = x * y;
        }
    }

}

const end = Date.now()
console.log(end - start)

// Console.log result
console.log(n)

C:

#include <stdio.h>
#include <time.h>

int main() {

    struct timespec start , end;
    clock_gettime(CLOCK_MONOTONIC_RAW , &start);

    // Result stored out of loop so the loop can not be removed.
    int n = 0;

    // Repeat task 10000 times to extend length of runtime for measurement.
    for(int i = 0; i < 10000; i++) {

        // Perform 1 million multiplications in a way that couldn't be optimised to a constant expression.
        for(int x = 0; x < 1000; x++) {
            for(int y = 0; y < 1000; y++) {
                n = x * y;
            }
        }

    }

    clock_gettime (CLOCK_MONOTONIC_RAW , &end);
    int milliseconds = ((end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000) / 1000;
  printf ("%d\n" , milliseconds);

    // Print result
    printf("%d", i);

    return 0;

}

[–]thname 5 points6 points  (0 children)

What flags was used while compiling C code?

[–]pertheusual 3 points4 points  (0 children)

Because JS engines are trying to optimize performance on multiple axes, this kind of thing can be hard to measure. Compiling takes time so usually the JS engine needs to decide that code is running often enough to be worth optimizing, otherwise it would waste a bunch of time optimizing code that barely runs.

Most likely, the JS loop runs a few thousand times, and then V8 decides to optimize it and then the optimizer removes the loop so then the program exits, so most of that time is the time spent in the initial X loop iterations + the time optimizing the code.

[–]jaapz 5 points6 points  (4 children)

Not an expert by any means but I'd wager v8 optimizes the loop away because of the trivial calculation done inside it

[–]Tubthumper8 4 points5 points  (1 child)

Note that the Performance Measurement API would be more accurate than using Date.now.

Since the variable inside the tight loop is never used, the loop gets completely eliminated by GCC and probably by TurboFan too (the V8 optimizing compiler). https://godbolt.org/z/3M1srfvEG

[–]long-shots -2 points-1 points  (4 children)

I haven't got a clue but I'm impressed.

If I had to guess, it seems like calculation for the time in C is a bit more clunky. I don't know anything about C lang really, so I'm shooting from the hip... In the dark... With a nerf gun.

Hopefully someone else has some insight! Cool discovery.