4 ms·
great post, this part > For reference, a Python version of this same loop runs in 1 minute and 38 seconds … a dynamically-typed bytecode interpreter is not a g
by iovrthoughtthis 6y ago
great post, this part
> For reference, a Python version of this same loop runs in 1 minute and 38 seconds … a dynamically-typed bytecode interpreter is not a good choice for heavy integer arithmetic.
made me wonder about other dynamic langs.
ruby (3.0.0) and lua (5.4.2) ran it in 1:30 and 1:03 respectively but node (12.22.0) ran it in 0:01. how is node so quick?
- sfifs 6y agoYears of Google engineers squeezing performance for JavaScript heavy web pages maybe?
- iovrthoughtthis 6y agoThat is certainly how, but why? What have they done that enables it to do that?
- cdogl 6y agoChrome's V8 engine made quite a stir around the turn of the last decade because it it compiled JavaScript down to native code. It would even watch how code was called (if it couldn't statically figure out a safe way to compile it) and generate native code for the happy paths in hot code paths. I simply assume that all browsers do this now because we take it for granted now that JS is reasonably fast, but I could be wrong. I credit much of Chrome's success to V8. It's a marvel of engineering. Shame that the rest of Chrome hasn't been held to such a high standard.
- josefx 6y ago> I simply assume that all browsers do this now because we take it for granted now that JS is reasonably fast, but I could be wrong. Chrome came out a month after Mozilla published its TraceMonkey JIT. So JITing JavaScript has been the standard thing to do since the end of 2008.
- KMag 6y ago... and TraceMonkey was based on open-sourced Macromedia/Adobe code from the ActionScript 3 VM. AS3 was loosely based on a draft of the ill-fated ES 4, but I'm not sure if it was a strict superset of an earlier ECMAScript standard. It's possible that the Flash AS3 VM had been JITing all of ES2 or ES3 earlier than TraceMonkey came out.
- pjmlp 6y agoHere, have some fun, "V8 Internals For JavaScript Developers" https://vimeo.com/312113316 https://vimeo.com/312113316 https://v8.dev/blog https://v8.dev/blog
- elcomet 6y agoNode uses a JIT compiler (V8) and is not interpreted. This is why it's so fast. You could compare it with PyPy, a just-in-time compiler for python. For example, on my machine, the same program runs in 2:12 min with the regular python interpreter, and in 1.57 seconds with pypy.
- thysultan 6y agoNodeJs and Go can and probably are optimizing the loop away, the authors attempt to assign the sum to result does not prevent this from happening.
- benhoyt 6y ago> can and probably are optimizing the loop away Why do you think that, and how would it do that and still do the calculation? My timing tests showed that Go didn't optimize the loop away (if I counted to 10 billion it took about 10 times as long), and godbolt.org also shows the loop in Go's assembly output: https://www.godbolt.org/z/d38xfGYr6 https://www.godbolt.org/z/d38xfGYr6
- Joker_vD 6y agoErlang/OTP 22 (on my machine™, of course) runs it in 9.3 seconds when invoked from the compiled module, and in at least in 5 minutes when invoked as a fun defined in the shell: "at least" because I did not have enough patience to wait until it actually completes.
- deweller 6y agoFor reference, this runs in 13s in PHP 8 on my ARM Mac. PHP 8 uses a JIT compiler.
- inbx0 6y agoWell, for one thing, if you copied the numbers from the example as they are defined, then JS will be dealing with simple floating point numbers and actually gets the answer wrong (but gets there fast!). The answer is beyond 5E+17 but JS's Number.MAX_SAFE_INTEGER is only 9E+15. The results start getting weird after that. To get the correct answer, we need to convert some of the numbers to BigInts var result = 0n function main() { var sum = 0n var i = 1 while (i <= 1000000000) { sum = sum + BigInt(i) i = i + 1 } result = sum } main() but after that the result is not quite so impressive anymore Now using node v15.6.0 (npm v7.4.0) time node index.js node index.js 51.50s user 0.40s system 103% cpu 50.072 total
- zeugmasyllepsis 6y agoThank you for providing details and not just assuming the JIT was the cause of the difference.
- josefx 6y agoRuby has its jit disabled by default as far as I can tell^1 . Lua exists in both jit compiled and interpreted forms. Python definitely is interpreted unless you run it through pypy. JavaScript is basically the only language in that lineup that enables just in time compilation with most of the "default" runtimes. ^1 Most likely because it generates C code and has to call an external C compiler to get things done.
- jerf 6y agoIf you think about what something like CPython is doing, you are maximally pessimizing the way the implementation works by adding integers together. You get all the dynamic type manipulation and management that is occurring all the time, but then when it comes time to do the 'payload' of the operation it is doing, it's a simple ADD operation that can complete in (amortized) less than one cycle. It's the worst case for something like CPython. All that work a dynamic language is doing has much better performance characteristics when you get to a "payload" that is like running C code to find a substring match in a string, or something else non-trivial that helps to amortize all the bookkeeping with more real work. As for why Node runs quickly, adding integers (or floats or whatever) is easy mode for a JIT. This is basically why you see JIT numbers like speeding up the original implementation by factors of literally over 100x, yet you can't expect that multiplier against "real code", for essentially the same reason only now on the other end; the JIT can very successfully cut out all that work CPython or similar implementatiors are doing if you have completely stable types that it can infer (especially something as simple as a single int or float), but now the "payload" is preventing the JIT from posting big numbers in improvement when you have more complicated operations because the JIT isn't going to be able to improve something like a substring search. If you get something like NumPy where you can feasibly write a program that will spend 99+% of its time in highly optimized matrix code, a JIT can't help much with that, because even if it reduced the bookkeeping to zero, you've still got to do all that matrix work. Statically-typed compiled languages basically bypass the whole "do lots of expensive bookkeeping to permit very flexble coding styles, then write a JIT that figures out how to bypass that expensive bookkeeping as often as possible" to simply constrain you to write in the fast subset in the first place.
- nonameiguess 6y agoNote that Python tends to get around this by providing a wide variety of fast builtin functions to do stuff that would otherwise be slow. $ time python -c 'sum = 0 > for i in range(1000000000): sum += 1' real 0m56.655s user 0m56.646s sys 0m0.008s Compare to: time python -c 'sum(range(1000000000))' real 0m8.693s user 0m8.684s sys 0m0.008s In reality, anyone is going to do heavy arithmetic using Numpy anyway: $ time python -c 'import numpy as np; np.sum(np.arange(1000000000))' real 0m2.022s user 0m1.661s sys 0m2.061s As a nice bonus, you get hardware speed but it still gives you the right answer, unlike Node trying to use limited precision primitive types.