9 ms·
We've been lied to: JavaScript is fast
- deleted 5y ago[deleted]
- encryptluks2 5y agoThere are many things to consider in addition to just JS, like bloated frameworks, dependency hell, startup times... yeah a single benchmark may not show much difference, but when you consider everything else there is a noticeable difference between a JavaScript CLI app and Go/C CLI app.. I can visibly even see a difference for CLI, but my process manager clearly shows any desktop GUI use for JavaScript is severely less performant than most native counterparts.
- JoeBot3000 5y agoSuper true, and JS frameworks haven't always been kind on their CPU usage (cough AngularJS). I think desktop GUI apps also have browser rendering engines chewing resources.
- mohanmcgeek 5y agoI've never heard anyone tell me that JavaScript is slow. I remember node.js benchmarks in the front page of hn showing it to be as fast as hotspot jvm. And this was pre 2012. And since node is single threaded, you had to run n.cores instances of your app. For the longest time as a kid what I was lied to was that Java was much slower than Ruby/Python and my perception was painted by the startup times.
- mc13 5y agoJava is probably 10 times faster than JS and Pyrhon.
- galaxyLogic 5y agoIf you say "probably" doesn't that mean it is not really certain if it is that way? May be, may be not. Maybe probably 10 times faster?
- mohanmcgeek 5y ago> Java is probably 10 times faster than JS I don't wish to start a language flame war here. We're beyond that. But by what measure? Doesn't this depend on what you're doing?
- dantodor 5y agoYes but. https://dev.to/jaredcwhite/the-shocking-immaturity-of-javascript-c70 https://dev.to/jaredcwhite/the-shocking-immaturity-of-javasc...
- JoeBot3000 5y agoI don't know that I agree with this article, while there is absolutely a problem with package/framework/build tools churn that makes development super painful, I don't feel that developing with JavaScript has been any more painful than developing with VB, C#, PHP etc was in the past. All languages have their pain points, and modern JS/TS, while undisputably having their quirks, aren't particularly more quirky than JS (IMHO)
- snek_case 5y agoI recently got back to a web app with node I'd been working on two years ago, and I'm finding packages deprecated, command-line arguments that no longer work, etc. Lots of breaking changes all the time. There's definitely something worse than average with the node development experience. Things are quickly changing and at the same time very poorly documented.
- tannhaeuser 5y agoWell, why did you choose packages that change quickly and are poorly documented then? There are packages that, OTOH, have up to zero dependencies, and haven't changed in years; if you consider those stale, I guess nobody can help you.
- snek_case 5y agoI chose the most basic and common packages I could find (terser and rollup), and the changing, no longer valid command-line options I was talking about were command-line options of the npm package manager.
- 5y ago
- rileymat2 5y agoIn the past when I have written basic benchmarking code like the first example, the compiler will typically optimize all the code away. I am curious why this did not happen or what optimization flags were passed in. If it was prevented optimization through flags, is this a fair test?
- JoeBot3000 5y agoFor lower numbers this completely optimizes away for me too, but after a certain point the loops stays in place (with no flags)
- rileymat2 5y agoThanks.
- jeroenhd 5y agoI've run the C example code through Godbolt with -O2 and... it removed all the loops because the result wasn't used. Then I added a printf statement and the algorithm came out without too many strangeness. When I reduce the amount of iterations, the compiler inserts a constant where the calculation would've taken place. The amount of iterations in the benchmark seems to be too high for the compiler to evaluate and optimize out. The clang seems to stop any full evaluation at exactly 101 iterations, meaning there's probably a default limit somewhere puts the limit on a nice, round 100. I've taken the code, increased the amount of loops by a factor of 10 (to make the differences more pronounced), added a printf/console.log to make sure the loops themselves don't get thrown away (clang does that with -O2) and on my laptop (i7-10750H) the C code, compiled with clang 12, runs 10000000000 iterations in 9.462s whereas the same number of iterations in Node 16 runs in 19.990s. That's more than a 2x execution duration, with more than a 100% speed difference. For comparison: Java runs in about 9.846s, from the command line (java code.java, no compilation step). C# (dotnet 5) runs in about 9.736s after compilation; compilation takes about a second. Rust runs in about 9.435s after about 0.47s of compilation. Kotlin runs in 9.640s, but it took a few seconds to be compiled into a JAR first. Python 3.9.7 takes forever, but I think that's because its arbitrary length number implementation is trying to make it output the correct result instead of faking it like the other programs are doing. PHP 8 also didn't really stand a chance at over 90 seconds. JS would probably win in a huge code base consisting of mostly dead code (node_modules, anyone?) where compilation would get in the way of quick edit-run-verify loops, but only starting from scratch without a compiler cache set up. From what I can tell, JS certainly isn't _slow_ like PHP, but it isn't _fast_ either. It's somewhere in between the old interpreters of old and compiled/runtime-JIT'ed languages. If anything, this algorithm benchmark would indicate that you're probably better off running C#/Java rather than C/Rust because of the negligible performance difference with the huge benefits of language safety with no effort. This benchmark is far from normal program code, of course, so it doesn't really prove anything. It should be noted that NodeJS outputs the result as "Infinity" whereas C and other languages creates a value that's clearly been bit-wrapped and overflown quite a bit. This can be an advantage (because Infinity + anything = Infinity) or a disadvantage (float math) but that's just how the language works. Edit: looking at the rest of the code, I've also benchmarked the loop vs functional approach (Node 16, same device). // Generate numbers numbers = []; for (let i = -10000000; i < 10000000; i++) numbers.push(i); lowest = numbers.filter(x => x > 0).sort((a,b) =>a-b)[0]; console.log(lowest); ^ this runs in 1.007s numbers = []; for (let i = -10000000; i < 10000000; i++) numbers.push(i); let lowest = Infinity; for (const i of numbers) { if (i > 0 && i < lowest) { lowest = i; } } console.log(lowest); ^ this runs in about 0.629s I wouldn't call a near 40% speed difference "only 2ns". The functional approach is very comfortable to program in, but it comes at a real cost and should definitely be avoided complex in algorithms. Java handles streams very poorly. However, LINQ is quite fast: var numbers = new long[20000000]; for (var i = -10000000L; i < 10000000L; i++) numbers[i + 10000000L] = i; Console.WriteLine("{0}", numbers.Where(x => x > 0).OrderBy(i => i).First() ); ^ this runs in 0.346 seconds. That doesn't make it quite optimal, though: var numbers = new long[20000000]; for (var i = -10000000L; i < 10000000L; i++) numbers[i + 10000000L] = i; long lowest = 9999999999L; foreach (var i in numbers) { if (i > 0 && i < lowest) lowest = i; } Console.WriteLine("{0}", lowest); ^ This runs in 0.151s These examples aren't very conclusive either. Rust's loop version runs in 0.100s and a shitty iterator version that collects the entire iterator and sorts it runs in 0.130ms. Again, the real fight here seems to be between C# and something closer to the metal.
- slavik81 5y agoThe given C program has no input or output and exhibits undefined behaviour (signed overflow). Even with the most rudimentary of optimizations, this program will execute in constant time. https://godbolt.org/z/YdshfqdWs https://godbolt.org/z/YdshfqdWs
- armchairhacker 5y agoJavaScript is still really slow compared to compiled languages - C, C++, even Java. That super-simple benchmark even after JIT is still 2-3x as slow as the C version, and more complex code can’t be optimized nearly as well. I can confidently say that games and apps in the browser and Electron are noticeably slower than other apps. You don’t see many web games because the graphics required for games today can’t really be rendered on the web in 20+ FPS, at least until WebGL2/WASM/WebGPU get better support. But JavaScript is fast enough. Because the vast majority of programs, especially websites, don’t need really fast code like C. They don’t need to redraw every frame, don’t need 3D capabilities, don’t need to perform expensive computations, etc.. If you need a fast program that does those things 99.9% of the time you can just make it a real app, or you can write the fast parts in WebAssembly. At the end of the day computers are very fast, so a program could be written in any language as long as it isn’t doing anything super performance-needing and the compiler/interpreter has has half-decent optimization.
- brundolf 5y ago> I can confidently say that games and apps in the browser and Electron are noticeably slower than other apps In my experience the great majority of perceptible slowness in browser apps comes from DOM reflows, not JavaScript
- josephg 5y agoI’ve also seen plenty of web apps which load slowly because they load too much javascript. Most of it unused. I was asked to help out with one project my coworkers were working on where the bundler was pulling in multiple copies of momentjs, each complete with its own copy of the global time zone database. The page loaded way faster once we cleared that out. A few years ago I noticed most websites just seemed slower than they should on my computer. The culprit turned out to be metamask (an etherium wallet extension). It was adding 700 kilobytes of javascript to each page load thanks to web3. It also exposed my etherium address to any website that asked. The JS was cached, but just parsing that much code added noticeable lag to every page load. Everything felt zippy once I uninstalled that.
- strken 5y agoIt was never slow. The event loop is very performant compared to a language with a GIL and a culture of synchronous IO (looking at you, python and ruby), and V8 is amazing. I notice that the author doesn't give any links to claims that JavaScript is slow, and the first two pages of search results are either discussions of how fast JS is or guides to JS performance. Who exactly was lying?
- pjscott 5y agoThe old JS interpreters were actually quite slow! That's why they made V8 in the first place. Speed was its big selling point.
- strken 5y agoOh yeah, sorry, I should have said nodejs (which has been V8 from day one) was never slow. Some of the older browser implementations were shocking. I should also add a massive asterisk to "never slow", clarifying that it's usually within a couple of orders of magnitude of compiled code, and faster than that for glue language tasks that let it call native code for the heavy lifting.
- laurencerowe 5y ago> It was never slow. The event loop is very performant compared to a language with a GIL and a culture of synchronous IO (looking at you, python and ruby), and V8 is amazing. Having written a lot of code in both Python and JavaScript I disagree here. It's not really the GIL slows down Python compared to JavaScript, that's more down to the lack of a comparable JIT. Yes there's PyPy but it has received a fraction of the investment of V8 and Python is a more complex language to create a good JIT for. Support for multithreading (albeit without parallelism) may be one of a number of factors those complicating factors. JavaScript doesn't need a GIL because it doesn't support multithreading. WebWorkers are more akin to Python's multiprocessing since objects are not shared between threads. You have to be careful doing anything compute intensive (e.g. JSON.parse of a large blob of JSON) when using cooperative (async) rather than preemptive (threads) concurrency since you may inadvertently block the event loop, substantially increasing latency for other tasks. Handing off such tasks to a thread pool is harder in JS than Python since you can't easily deferToThread as you can in an async Python program. Cooperative multitasking has its advantages too of course. An event loop provides a clear boundary for efficiently batching calls which can be awkward with synchronous code. Folks have been writing network servers and in Python using asynchronous techniques for decades now. Twisted is almost 20 years at this point and asyncore was added to the standard library even earlier.
- fenomas 5y agofunction main() { let myNum = 0; for (let i = 0; i <= ITERATIONS; i++) { myNum *= i; myNum++; } } I'm a little surprised that turbofan doesn't JIT that into an empty function, given that it has no return value or side effects.
- titzer 5y agoIt doesn't eliminate loops, but it almost certainly is an empty loop after the full pipeline of optimizations and scheduling. I'm guessing the C version is unrolled or even completely eliminated, because LLVM will definitely eliminate empty loops.
- fenomas 5y agoIt feels like it ought to, but I did some light testing in the browser and it seems that V8 never optimizes it into an empty loop, even after it's hot code for a long time. I know turbofan does LICM, but I don't think it knows how to catch cases like this.
- recursive 5y agoThe second code comparison is supposed to point out that the second version. Unfortunately, still not easy enough to read to avoid bugs caused by javascript's "unique" approach. `findSmallestPositiveValue([2,11])` gives the wrong value for the supposedly better function. > Even if the second function takes twice as long as the first, we are in the realm of nanoseconds. How can you possibly know this? You can make either function take arbitrarily long by increasing the size of the input. The second one scales worse. I see no reason to assume an upper bound on the input size given. If you want the behavior to be obvious at a glance, just name the function what it does. Just like it already is. Or leave a block comment. The argument here is that javascript is so fast that it doesn't matter what you write because you don't have to worry about that. I just don't know how to reconcile that with the fact that I regularly see websites that have noticeably slow javascript "startup" times.
- JoeBot3000 5y ago> You can make either function take arbitrarily long by increasing the size of the input. The second one scales worse. I think this is the point, the second one scales worse - absolutely (ignoring the bug you mentioned) however it really is more readable, and because V8 is so fast, why not use the second version? Block comments and function names are important, but if someone needs to modify the code to add or change functionality nothing beats simple & readable code.
- recursive 5y agoYou can use .reduce((acc, curr) => curr > 0 && curr < acc ? curr : acc) and have the best of both worlds. In some cases, there are persuasive arguments to favor readability over performance, but I don't find this particular example very convincing.
- JoeBot3000 5y ago> I regularly see websites that have noticeably slow javascript "startup" times. True, same as any language or applications fast CPUs shouldn't be a free chance to completely ignore writing efficient code. Although I bet those websites are slow because they are doing silly things like adding 1000 items to the DOM one by one rather than being slow because someone didn't optimize their use of .forEach()
- synergy20 5y agoI hope someday we can convert python to javascript, or even embed python into web pages. There are too many languages these days, for me at least.
- synergy20 5y agoTo answer my own question there are 2 active python to js efforts: https://brython.info/static_tutorial/en/index.html https://github.com/qquick/Transcrypt
- williamstein 5y agoHere's a third called "JPython", which I've been writing lately for fun: https://github.com/sagemathinc/JSage/tree/main/packages/jpython https://github.com/sagemathinc/JSage/tree/main/packages/jpyt...
- deleted 5y ago[deleted]
- kccqzy 5y agoStop writing C code like this author does! C is a language for experts and there are lots of things that are wrong here. Especially when writing benchmark code, when you do want the compiler to optimize. > int main() The easiest way to find someone who's inexperienced in C is to find someone who declares a function that takes no arguments with an empty pair of parentheses. In C but not C++ you need to write "void" inside the parentheses. > myNum *= i; Signed integer overflow is undefined behavior. You are lucky if the compiler didn't just replace the whole thing with __builtin_unreachable(). Next the function doesn't use the computed variable. A compiler can just optimize the whole thing out. The point I'm trying to make is that C is a difficult language to write; especially so when you want to benchmark.
- mhh__ 5y agoAdding unreachable to random places you think the compiler should already know about can yield some perf wins! Compilers are quite chaotic when you get deep in the passes.
- josephg 5y agoAll the C compilers I know of can handle "int main()" just fine. Is there any practical reason to prefer "int main(void)"?
- pjscott 5y agoThe compiler used, Clang 13.0.0, does indeed optimize out everything except for the "return 0;" at the end unless you compile with -O0 to disable optimization. I'm not sure whether the author benchmarked with -O0 or benchmarked a no-op, but either one is probably a mistake.
- moonchild 5y ago> The easiest way to find someone who's inexperienced in C is to find someone who declares a function that takes no arguments with an empty pair of parentheses. In C but not C++ you need to write "void" inside the parentheses. C11§6.7.6.3p14: > An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied. That declarator is part of a definition. Even if it were not, the elision of a function's formal parameter list does not matter very much if, like 'main', that function is usually not explicitly called.
- mhh__ 5y agoSome anecdata: Node was roughly as fast as dmd, the D compiler for fast iteration. The LLVM and GCC based D compilers are hugely faster. By using JavaScript you are throwing away something like 2-3x. There will be cases where JS can keep up because it reduces to a local loop which gets optimized the same way as any other language, but all those little other bits add up. Same with Python. If you do everything in numpy you can be very fast, but what happens if you don't? You could use a fancy jit but now you have a deeper stack with no control over the emitted code - nice if you already have the code, but not ideal.
- pizlonator 5y agoThose are some really bad benchmarks. JavaScript is probably like 10x or 100x slower than C or similar languages when you write something bigger and any of the following happens: - the ratio of code size to run time is too big. Then the jit can’t keep up. - you use a lot of value types. Them be structs in C, no need for allocation. In JS them be objects. The JS VM will try to escape analyze them, and it will succeed some of the time, but fail enough of the time to cause massive punishment. - you churn objects while having a large base heap size and the generational hypothesis holds only a bit. Then you’ll wait for the GC a lot. - you have a class hierarchy with many descendants and you often access properties or call methods on the base type. Then vtables or whatever work great but JS inline caches blow up. - probably lots of other conditions. Write enough code and at least one of these will hold and you’ll be slow in JS, fast in C. (Source: I work on JSC and I implemented a lot of its optimizations. Benchmarks like the ones in this post are the sort of thing my JITs eat for breakfast. It’s cool to see people throwing me softballs but I like to be honest about what the technology I work on is capable of.)
- pbadenski 5y agoTo what extent writing your code in WebAssembly (eg Rust) can help with those points (eg structs in C argument). It would still run in a JS VM so I'm guessing a bit, but not to the full extent?
- pizlonator 5y agoWebAssembly helps a lot, but doesn’t solve the jit issue. WebAssembly also introduces its own issues since it’s a BYORT system (bring your own runtime). So, there’s more to JIT (your language’s whole runtime) and more to hold in memory (your language’s whole runtime). You might say, “but pizlonator, every language has a runtime”, to which I’d say: yes but usually that shit gets shared by every running instance somehow. In WebAssembly every instance pays for its runtime’s memory footprint and it’s quite likely that every instance has a different runtime so the code isn’t shared either. Basically, WebAssembly is knee-capped on memory footprint by design. JS isn’t.
- 5y ago
- maxdo 5y agothe big problem of js on backend is quite bad profiling of nodejs apps.
- reilly3000 5y agohuh? --prof works fine for me. There are lots of other options out there, like clinic.js for example. https://clinicjs.org/ https://clinicjs.org/
- tgsovlerkhgsel 5y agoJavaScript has incredibly good async APIs (promises, async/await, and the seamless integration between the two). As a result, I'm convinced that software written in JS is much more likely to actually perform long-running (IO-bound) tasks in the background/in parallel, simply because it isn't a huge pain to do it. The actual execution speed matters a lot less at that point.
- invalidname 5y agoYou would think that... But unfortunately this isn't the case. You end up running into the global lock problem where something is stuck deep inside NodeJS and you're at 30% CPU utilization with no idea why. Because everything is async you have no way of understanding what the hell is broken without going into the NodeJS source code and debugging that. Profiling NodeJS is futile because of its async nature. You end up with a lot of noise and no substance. I'm looking forward to project Loom which would bring Java threads into hybrid green/native mode. That would deliver throughput as fast as NodeJS but with the performance of Java and clarity of simpler stack traces.
- tgsovlerkhgsel 5y agoI was thinking more about client/interactive software. For server applications handling many requests in parallel anyways, it's a different story.
- galaxyLogic 5y agoRight, async processing performance is hard to predict, but still it helps to be able to use async/await. This brings to my mind the current logistics crisis in the global trade. Lots of parallel tasks and traffic jams and all of a sudden stores don't have stuff and prices are going up and we don't know when will it be back to normal. That is what Node.js can feel like, because it is hard to understand what tasks execute when and who is waiting for what.
- rank0 5y agoThat's the most ridiculous benchmark I've ever seen. Real world workloads are absolutely nothing like the example in this blog post. Also, why ignore memory usage and JS VM startup time? Why ignore the massive dependency tree that comes with JS frameworks? There's multiple factors to consider when assessing language performance. JS is far less efficient than other languages...but that's honestly fine. There are plenty of use cases for the language where your application doesn't have to be incredibly performant.
- s9w 5y agoHe didn't enable -O levels, otherwise the C code would run in 0 time since it contains only no-opts which would be optimized away by every compiler.
- galaxyLogic 5y agoIsn't is the case that most programs we normal humans interact with spend most of their time doing IO, rather than number crunching? Are there languages that are better at doing IO than others?
- heisenbit 5y ago> With the safety net of fast engines, we can sacrifice the 'fastest' implementation of our functions for more readable & maintainable versions. Followed by a search smallest function which is linear in time optimized to a function using filter/sort which is n*log(n) in time. Impressive indeed.
- eliwang 5y agoNo mention to esbuild here? Check it out. The speed bump is surreal.
- pbadenski 5y agoThis article is really a reiteration of a https://wiki.c2.com/?SufficientlySmartCompiler https://wiki.c2.com/?SufficientlySmartCompiler argument. I would've sticked to a massively less appealing, but more closer to reality: "We've been lied to: JavaScript is pretty fast, but..." argument.