Why is this NodeJS 2x faster than native C?

不想你离开。 提交于 2019-12-04 19:29:36

问题


For the sake of a presentation at work, I wanted to compare the performance of NodeJS to C. Here is what I wrote:

Node.js (for.js):

var d = 0.0,
    start = new Date().getTime();

for (var i = 0; i < 100000000; i++)
{
  d += i >> 1;
}

var end = new Date().getTime();

console.log(d);
console.log(end - start);

C (for.c)

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

int main () {
  clock_t start = clock();

  long d = 0.0;

  for (long i = 0; i < 100000000; i++)
  {
    d += i >> 1;    
  }

  clock_t end = clock();
  clock_t elapsed = (end - start) / (CLOCKS_PER_SEC / 1000); 

  printf("%ld\n", d);
  printf("%lu\n", elapsed);
}

Using GCC I compiled my for.c and ran it:

gcc for.c
./a.out

Results:

2499999950000000
198

Then I tried it in NodeJS:

node for.js

Results:

2499999950000000
116

After running numerous times, I discovered this held true no matter what. If I switched for.c to use a double instead of a long in the loop, the time C took was even longer!

Not trying to start a flame war, but why is Node.JS (116 ms.) so much faster than native C (198 ms.) at performing this same operation? Is Node.JS applying an optimization that GCC does not do out of the box?

EDIT:

Per suggestion in comments, I ran gcc -Wall -O2 for.c. Results improved to C taking 29 ms. This begs the question, how is it that the native C settings are not optimized as much as a Javascript compiler? Also, what is -Wall and -02 doing. I'm really curious about the details of what is going on here.


回答1:


This begs the question, how is it that the native C settings are not optimized as much as a Javascript compiler?

Since C is statically-compiled and linked, requiring a potentially lengthy build step of your entire codebase (I once worked in one that took almost an hour for a full optimized build, but only 10 minutes otherwise), and a very dangerous, hardware-level language that risks a lot of undefined behaviors if you don't treat it with care, the default settings of compilers usually don't optimize to smithereens since that's a developer/debug build intended to help with debugging and productivity with faster turnaround.

So in C you get a distinct separation between an unoptimized but faster-to-build, easier-to-debug developer/debug build and a very optimized, slower-to-build, harder-to-debug production/release build that runs really fast, and the default settings of compilers often favor the former.

With something like v8/NodeJS, you're dealing with a just-in-time compiler (dynamic compilation) that builds and optimizes only the necessary code on the fly at run-time. On top of that, JS is a much safer language and also often designed for security that doesn't allow you to work at the raw bits and bytes of the hardware.

As a result, it doesn't need that kind of strong release/debug build distinction of a native, statically-compiled language like C/C++. But it also doesn't let you put the pedal to the metal as you can in C if you really want.

A lot of people trying to benchmark C/C++ coming from other languages often fail to understand this build distinction and the importance of compiler/linker optimization settings and get confused. As you can see, with the proper settings, it's hard to beat the performance of these native compilers and languages that allow you to write really low-level code.




回答2:


Adding the register key word helps as expected

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

int main () {
  register long i, d;
  clock_t start = clock();
  i = d = 0L;

  for (i = 0; i < 100000000L; i++) {
    d += i >> 1;
  }

  clock_t end = clock();
  clock_t elapsed = (end - start) / (CLOCKS_PER_SEC / 1000);

  printf("%ld\n", d);
  printf("%lu\n", elapsed);
}

and compile with the C compiler

 cc     for.c   -o for

./for ; node for.js

returns

2499999950000000
97
2499999950000000
222



回答3:


I've also done some testing calculating prime numbers and I've found that Node.js is about twice as fast as C see here.

When you have a very simple counting type of loop, the -O2 optimization can sometimes convert the output to a simple formula without even iterating the loop. See Karl's Blog for an explination. If you add something more complicated to the routine it is likely node.js will be faster again. for example I added a devisor term into your sample program and the c -O2 optimization was no longer able to convert it to a simple formula and node.js became faster again.

I am still baffled as to how node.js can be caster than c at simple integer calculations, but in every test I've done so far it is faster. I've also performed some tests with bitwise calculations that I haven't posted yet and still node.js was faster.




回答4:


node.js and C are distinct in that node.js is interpreting JavaScript whereas C is compiling code to machine language. As such, both are handled differently. For node.js, you simply run the .js file. C is much different from this. When you are compiling code to machine language using GCC, you must supply compiler optimization settings aka "flags." Your node.js program is actually slower than the C program if you specify the -O3 flag to GCC. In fact, the node.js program took twice as long for me as the C program did. You stated that you would like to know more about what the C compiler does to optimize code. This is a complex topic/field and I highly recommend reading this Wikipedia article on compiler optimization to learn more.

In short, you made an unfair comparison because you did not optimize your C code.



来源:https://stackoverflow.com/questions/27432973/why-is-this-nodejs-2x-faster-than-native-c

工具导航Map

JSON相关