I used to believe that javascript was slow, C was fast and that there was no way to change this because of the way that javascript is interpreted and not compiled. But the more I've learnt about the benefits of "on the fly" / "just in time" optimising compilers, the more I'm convinced this is the future of computing. Being able to use multiple threads and hence multiple cores simultaneously to optimise what is actually a single threaded piece of code is quite amazing. And then being able to write code that is insanely portable and optimised on any platform is great.You can take advantage of SIMD, hyperthreading, multicore, large caches... without knowing if they're available ahead of time.
Sure, you won't beat C for low memory devices or hardware that you have complete control over but for 90% of use cases javascript actually makes sense and can be the most performant.
> javascript actually makes sense and can be the most performant.
Thanks to V8 you can definitely achieve good enough performance with JavaScript for most applications, and the ergonomics are pretty great. But it’s not true that performance rivals or beats C in the general case. The ‘sufficiently advanced compiler’ rainbow is something that Java has spent decades chasing. I think it’s fair to say at this point that humans are better at writing C code than we are at writing clever compilers.
It’s many of the exact ergonomics which make JavaScript useful and easy to use that also make it much harder to optimise. For example, what code should V8 generate for `x+=x` ? The optimiser might find that x is always a positive integer, but every time it gets doubled, it might cross the magic overflow point where it needs it’s representation swapped for a double. The generated code must check for this every time. Or consider memory management - For games I’ve heard of people making a per-frame arena allocation pools for values with a lifetime that won’t cross frame boundaries. This basically makes these allocations free and improves cache coherency. I’m happy that the JS GC is multithreaded now, but there’s no way a GC can compete with that. And unlike C, there’s no way in JS to override the allocation behaviour.
There are a million paper cuts like this which lower the ceiling of how fast optimised JS can run. I’m glad V8 will keep improving, but for my money the future of really fast JavaScript is Webassembly.
>For games I’ve heard of people making a per-frame arena allocation pools
Games are a special case where each ms counts. Globally, the number of LOC written for consumer or business software dwarfs that of game code and for these kinds of applications JS is well within the realm of performant enough. That assumes you're not doing your presentation layer using the DOM though which is still garbage wrt performance. Luckily there are things like Qt to make high performance UIs in though.
Yes I think we all agree. As I said in my post above:
>> Thanks to V8 you can definitely achieve good enough performance with JavaScript for most applications
The point I argued above isn’t that JS is slow. It’s that JS will probably always be slower than well written C code. I still write far more JS than C, because my time is usually more valuable than the computer’s.
That said, games are far from the only place where every ms counts. Performance matters in database servers, operating systems, real-time applications (eg robotics), text editor typing latency, UI responsiveness, 3d modelling software, video encoding, cryptocurrency mining, browser DOM rendering and so on.
I love JS, but native code is not a special case. Despite the best efforts of Electron, I suspect most of the globe’s aggregate clock cycles are spent running code written in languages other than JavaScript.
Thankfully the world of managed languages is not constrained to JavaScript, nor C is the only option for unmanaged ones, a language that we would consider no respectful games programmer would use, after all a good game engine should be written in Assembly.
Ironically it is now used as the language to beat.
I don’t use JS regularly because the ergonomics of async have been so poor. This might not be the case with recent async/await work, but I’ve also not had positive async/await experiences in other languages either. This is too bad because V8 seems like a dream compared to CPython or even Pypy, but I prefer Go to both, especially when it comes to I/O.
You make some interesting points about cases where JS will always be difficult to be optimized automatically.
I wonder if compiler hints can help in that regard? For example, if performance is important then you could annotate the 'x+=x' line to tell the compiler to not check for overflow.
Another option would be that the compiler deduces when exactly the statement x+=x would need to switch from integer to double. That's more difficult, and impossible in general, but not impossible in specific cases.
Sure, you can hint with OR: `x = x | 0; x = (x + x) | 0;`. Bitwise operators always work on 32 bit integers in js. I'm not sure whether the js vms actually need or use this information.
The reason most of the features of the JIT are needed is due to the lack of static typing in javascript. The most important thing the JIT does is figure out what types of things you are using and optimize for them.
There will always be some cost here, because at runtime you need to check if your assumptions still hold. Some of the time you can pull these checks out of loops, but sometimes you can't, and they can be costly.
There are a few things a JIT can theoretically do better. Inlining is the biggest one. For example, a JIT knows not to inline a function that never gets called, and it can also do inlining for indirect calls based on profile data. A JIT could also theoretically move unexecuted blocks out of line for better instruction cache locality. It could also check for aliasing at run time (with a bailout) and then optimize a loop assuming no aliasing.
Unfortunately for JITs, AOT compilers can do a pretty good simulation of most of these things with PGO, that is running a scenario with an instrumented binary and re-optimizing using profile data.
Still can't bailout if an assumption is wrong, so it doesn't work for dynamic languages, but you don't really need bailout for much in static languages.
In my group at Oracle we're experimenting with running C using the same just-in-time compilation techniques that JavaScript uses, and sometimes we see it running faster than ahead-of-time native compilation, due to the effects of things like inline caching and profiling.
Reports of some jitted Java code running faster than C (after warmup) are old. I remember seeing those claims for some super hot VM in the old IBM System's journal from 2000, and yes, IIRC that was a VM written mostly in Java.
However, those isolated examples rarely if ever translated to high performance in real-world projects. The same issue has an experience report of the travails of IBM's San Francisco project. Performance was a huge issue that to the best of my knowledge they never fully resolved.
Heh ... one of my employees took it into their head to code up some arithmetic algorithms in C++ a month or so ago. We do not use C++ for anything, we are all Python and JVM based. But he decided that he was going to achieve an amazing win by optimising some numerical code to get order of magnitude benefits, and without asking invested 4 hours into coding it up. I wrote a naive implementation of the same thing in Groovy, of all languages. My implementation was initially 20 times as fast and I coded it in 30 minutes.
So he debugged some more and figured out that he misunderstood some of the inner workings of how vectors copy data and also that he did not understand the threading library he was using properly. He then fixed those two things. After this further exercise he reduced the difference to factor of 4. However he was never able to work out why my code was 4 times as fast as his C++ and abandoned it.
I know for sure that with appropriate expertise the C++ could probably be made to go perhaps twice as fast as my Groovy code. But the point is, none of the supposed benefits come automatically regardless what language you are using. And unless you flip over to GPU or FPGA accelerated methods, the final outcome is well and truly in the same ballpark anyway.
But all this is to say that "rarely translated" might be true at the for applications that are completely in the high performance domain. But for all the applications where the high performance code is in niches at the edge and there simply aren't resources or expertise to fully tune the native implementation ... I think it's translated all the time.
In my experience, writing java (or groovy here) in c++ results in horribly slow code which the jvm runs circles around, and it sounds like that's the problem your employee ran into.
> But for all the applications where the high performance code is in niches at the edge and there simply aren't resources or expertise to fully tune the native implementation
It's interesting you say this, because in my experience it's the JVM which requires absurd amounts of tuning and native programs which are much more consistent. The proper and easier way that native programs are written lends itself to fairly respectable performance, mostly because the object and stack model of say C or C++ is so much friendlier to the CPU than in most dynamic languages.
In general, for all that I hear statements along this line, I've only twice seen code to back it up, and the C was so de-optimized from the OCaml version that I suspect it was intentional - the author (same for each) was a consultant for functional languages, and in one case switched the C inner loop to use indirect calls for every iteration and in the other switched the hash function between the C and functional comparison.
In addition, a lot of the techniques used to write high-performance Java boil down to "write it like C". Avoid interfaces, avoid polymorphic virtual calls (as you can't avoid virtuals entirely), avoid complex object graphs, avoid allocating as much as possible...it's not nearly as nice as naive Java. Still nicer than C IMO. If your process segfaults you can know for certain that it's a platform bug.
The other thing that makes Java nicer than C is the ease and depth with which you can profile it to discover where the bottlenecks actually are. While it's certainly possible to profile in both cases, the runtime reflective and instrumentation capabilities of the JVM really add a lot of power to it.
This is great benchmark of the fundamental problems with say Java - the code itself is fairly simple and the JITs probably generate optimal code given their constraints, but the performance problems clearly show that the GC and pointer chasing really hinder your performance.
If you add in cases where simd, software prefetching, or memory access batching help, the difference will only grow.
It’s not native vs VM, but rather “has stack semantics/value types” vs “no stack semantics/value types”. In particular, OCaml’s standard implementation is a native, not VM.
Also worth calling out Go, which is rather unique in that it has stack semantics but it also has a garbage collector, so it’s kind of the best of both worlds in terms of ease of writing correct, performant code.
I should have been more clear I guess; I was comparing it to other popular languages. Few have value types and many that do (like C#) regard them as second-class citizens.
But go has an imprecise GC (in reference implementation) or stack maps (in gccgo), so the GC overhead is rather huge. It also lacks of compaction, so cache misses are not that good too.
Not sure what you mean by imprecise, but Go’s GC does trade throughput for latency. The overhead still isn’t huge if only because there is so much less garbage than in other GC languages. I’m also surprised by your cache misses claim; Go has value types which are used extensively in idiomatic code so generally the cache properties seem quite good—maybe my experience is abnormal?
perf shows how much time does GC eat, and that's quite a lot. Thus in the majority of benchmarks go lags behind java or on par with it at best.
>there is so much less garbage than in other GC languages
That is not true since strings and interfaces are heap allocated thus the only stack allocated objects are numbers and very simple structs (i.e. which contains only numbers), so you would have a lot of garbage unless you are doing a number crunching, which could be easily optimized by inlining and register allocation anyway.
You’re mistaken about only numbers and simple structs being stack allocated. All structs are stack allocated unless they escape, regardless of their contents. Further, arrays and constant-sized slices may also be stack allocated. I’m also pretty sure interfaces are only heap allocated if they escape; in other words, if you put a value in an interface and it doesn’t escape, there shouldn’t be an allocation at all.
I'm sure your strings are not stack allocated, they are statically allocated (and would be statically alocated in any language). Not sure about arrays, but dynamic arrays should be dynamically allocated do, your arrays are static probably. They would be heap allocated, if you would use make.
It doesn't matter whether they're stack allocated or statically allocated; neither is garbage, contrary to the original claim ("Go generates a lot of garbage except when dealing with numeric code"). The subsequent supporting claims ("structs with non-numeric members are heap-allocated", "struct fields that are not numbers are heap allocated", etc) were false (sometimes non-numeric members are heap allocated, but they're often not allocated and never because they're non-numeric and their container is never heap allocated on the basis of the location of the member data).
I think this matter is sufficiently resolved. Go trades GC throughput for latency and it doesn't need compaction to get good cache properties because it generates much less garbage than traditional GC-based language implementations.
>It doesn't matter whether they're stack allocated or statically allocated
It does. Any language could do static allocation, go is not different from java here, the problem is that in any real code nearly all your strings and arrays would be dynamic, thus heap allocated, as well as interfaces. Consider also that allocations in Go are much more expensive than in java or haskell.
We're talking past each other. My claim was that Go doesn't need compaction as badly as other languages because it generates less garbage. You're refuting that with "yeah, well it still generates some garbage!". Yes, strings and arrays will often be dynamic in practice, but an array of structs in Go is 1 allocation (at most); in other many other languages it would be N allocations.
> Consider also that allocations in Go are much more expensive than in java or haskell.
This is true, but unrelated to cache performance, and it's also not a big deal for the same reason--allocations are rarer in Go.
EDIT:
Consider `[]struct{nested []struct{i int}}`. In Go, this is at most 1 allocation for the outer array and one allocation for each nested array. In Python, C#, Haskell, etc, that's something like one allocation for the outer array, one allocation for each object in the array, one allocation for each nested array in each object, and one allocation for each object in each nested array. This is what I mean when I say Go generates less garbage.
A typical example, yeah. I've said about structs of ints already, it's not a common type unfortunately anywhere beyond number crunching, in which go sucks anyway.
In haskell you could have unboxed array with unboxed records. Check Vector.Unboxed.
Yeah, but you were wrong (you said other kinds of structs would escape to the heap). The innermost struct could have a string member and a `*HeapData` member; it wouldn't matter. The difference in number of allocations between Go and others would remain the same. The difference isn't driven by the leaves, it's driven by number of nodes in the object graph; the deeper or wider the tree, the better Go performs relative to other GC languages.
> In haskell you could have unboxed array with unboxed records. Check Vector.Unboxed.
For sure, but in Go "unboxed" is the default (i.e., common, idiomatic); in Haskell it's an optimization.
Regarding your last point, Crystal has the same features as go in that regard, while at the same time being vastly more expressive. This mostly due to the standard library in Crystal being so nice for work with collections (which perhaps isn't surprising as the APIs are heavily influenced by Ruby). Blocks being overhead free is another necessary part for this to work well.
Yeah, I often find myself wishing Go's type system were a bit better, but the reason I prefer it is because it's fast, easy to reason about, and the tooling/deployment stories are generally awesome (not always though--e.g., package management). So far I'm only nominally familiar with Crystal; I'll have to look into it sometime.
This is exactly it: dynamic languages give you OK ish performance and fast development speed. Fast C++ code requires a lot of expertise, this kind of expertise is expensive and there are diminishing returns too. I don’t know anything about expertise of your colleague in C++ but given that their first optimization was to eliminate some redundant copying, I suspect there is some more room for improvement. After unnecessary copying is removed it usually boils down to things like cache locality, better memory allocation discipline, data alignment, and sometimes knowledge of a better algorithm applicable to a particular situation (eg radix sort or perfect hashing or tries), judicial use of multi threading (in form of OpenMP), understanding whether single precision floating point is good enough etc etc.
I don’t think this is a property of dynamic languages. This groovy example is almost certainly the best case for the JVM (arithmetic, few allocations or polymorphism, etc). In other words, probably not taking advantage of the dynamism.
Dynamic languages can be fast by being well designed and simple (Wren) or highly optimized (JS) or both (LuaJIT). There’s also the experimental GraalVM, but this is definitely the exception and not the rule.
Writing performant C++ is actually not hard if you have rudimentary C++ knowledge. That said, rudimentary C++ knowledge is a lot more expensive than rudimentary knowledge of other languages. But the options aren’t just dynamic languages vs C++; there’s a ton of middle ground with VM languages like Java and C# and native languages like Go. The first two aren’t much harder than dynamic languages and I find Go easier than any dynamic language I’ve used to date (I’m a professional python developer). But all of these languages are on the order of half of C’s performance and 100X the speed of CPython or Ruby and 10X of JS.
I don’t think I’m following your point. That C# isn’t a VM language because there exist AOT compilers? That’s fine; my point is unrelated to the VM/interpreter/AOT taxonomy—just that dynamic languages aren’t particularly performant. I’m happy to concede the “C# is/isn’t a VM (even though 99% of production deployments are VMs)” point if it matters to you.
Are you going to let your employees get away with investing all of 4 hours into things you did not give them permission for? You haven't shown them who the alpha is until they ask you for permission to go to the bathroom.
From the late 90s until 2006, I believed in the sufficiently smart JIT. There were a few benchmarks showing corner cases of Java out-performing C/C++. I followed the research. I was particularly excited by Michael Franz's SafeTSA research on more JIT-optimized program representations. Surely we were just a few years from Java generally out-performing C/C++! I worked on the most popular Java desktop app.
Then I moved to Google, working on the indexing system and properly learned C++. I've since worked on several projects for several companies where the annual cost for compute time is easily 200 times my salary. These are systems where it pays to extensively profile both CPU and I/O and retain C++ optimization experts.
I still see the appeal of platform-independent binaries and dynamic optimization, but you really need the startup time and optimization cost advantages of ahead-of-time compilation. Hopefully the platform independence and dynamic optimization features would also be decoupled from the garbage collector.
HP Research had project dynamo that was basically a tracing JIT emulator for PARisc CPUs that ran on PARisc. It showed that binaries compiled with -O2 could get performace comparable to -O4 through dynamic recompilation.
Ideally, we'd distribute programs in a compact and compressed CPU-independent SSA representation similar to SafeTSA or LLVM bitcode. Installation would AoT-compile the binaries and keep around the SSA form for use by an HP Dynamo-like runtime optimizer. The AoT could compile functions and methods to lists strait-line extended basic blocks with a call to a trampoline loop or other techniques for lightweight instrumentation/tracing of native code. The dynamic optimizer wouldn't need to work with arbitrary machine code, only AoT compiler output. Also, it would never have to disassemble native code, but could always start by gluing together the SSA for each extended basic block in the trace.
A few CPU features could make native tracing and dynamic recompilation extremely light weight. Unfortunately, Intel and ARM are disincentivised to do so, as it would make it easier to migrate off of their intellectual property.
> sometimes we see it running faster than ahead-of-time native compilation
Edge cases. But obviously it's not comparable to something ahead-of-time compiled and hand profiled with PGO and stuff. Although in theory you could put that through JIT too, but it would probably just add overhead and only slow things down.
> sometimes we see it running faster than ahead-of-time native compilation, due to the effects of things like inline caching and profiling.
which is what java touts as being able to do. Unfortunately, i think this sort of speedup is really dependent on the application (and the developer using common idioms that is recognized by the JIT).
The difference in speed has a lot more to do with memory layout. Javascript will never be as fast for this reason.
You could make a JIT language as fast as C so long as it supported value types properly. Most programs in the "slow" languages spend most of their time chasing pointers around.
To elaborate on your point, its not just chasing pointers, it’s that those pointers point into random heap locations, each of which needs to be allocated individually and garbage collected. Also, because of the haphazard locations of these objects in the heap, you have way more cache misses than you would with value types.
I agree, though you'd be surprised at the difference in attitude between developers in each language.
I think JS is now within a factor of five of C, but JS programmers are significantly more blase about performance. In many cases they won't blink at a quadratic algorithm when a linear or nlogn one is possible, provided the quadratic algorithm is cleaner.
Ditto constant factors -- a C programmer might go out of their way to not iterate over a string more times than necessary, and a JS programmer won't. A C++ programmer might try to minimise lookups into a map, and a JS programmer won't. Some JS shops strongly encourage `array.forEach`, which is 6-7 times slower (on my machine) than an "old-school" `for()` loop.
For most web apps it makes no difference, but if performance ever does become a problem it can make it harder to fix. In my experience, profiles in "fast" languages in codebases written in the traditional paranoid style tend to be "lumpier" than profiles in "slow" languages written in the fashionable cavalier mode.
None of this is to judge anyone. I think being lazy and implementing an O(n^2) algorithm is perfectly reasonable when you know n is always going to be small, but I am cautious -- as much as the increased productivity is fun and addictive (and real in my day-to-day work), I've also seen the death by a thousand cuts first hand.
Welcome to 1995 and the wonderful world of Java. Except in Java you can use threads to use multiprocessing, so you can have efficient shared data structures but also hard to debug bugs.
C++ also does this. A variety of projects, such as MXNet, use JIT compilation. Native compilation gets you far, but there are times where dynamic compilation gets you farther.
I would argue that in 95 was recently the first jdk 1.0 release, it was until 2004 which came java 5 with java.util.concurrent which was such huge improvement for that time, since then is easier to write concurrent software, and since java 8 is a wonderful experience!
What's funny to me is how JavaScript has gone from something of a joke to a very well regarded, performant, and heavily used programming language. I can clearly remember the days when proposing any significant programming in JavaScript would get a developer laughed out of the room. Is there another example of a language that has had such a 180 degree turn around in respect and popularity?
JS had the advantage of being the default language of the web current younger generation of programmers grew up with, and then tons of resources were thrown at it.
I'm sure Python, Ruby and PHP would benefit just as much from such attention, although I guess Python has had that in scientific computing, and PHP with recent 7 version and FB.
You don’t achieve acceptance without a level of respect that comes from being useful. JS is really one of the most useful languages today.
You can argue whether everything moving to web apps (and I may be using the term web apps wrong, but I’m speaking about anything that runs in a browser) is such a good idea, it’s probably not, but it’s whats happening because it’s really fucking awesome in terms of getting things to actually work across multiple devices.
JS does something none of the other tech stacks does, in that it integrates really well with your current stack. I work in enterprise, we operate more than 500 different it systems, and most of them run on windows. So natural,y our operations ninjas are really great at Microsoft tech. Operating ruby, PHP, python and so on can certainly be done within the windows environment, but we’ve naturally been doing .net since it fits in better.
JS fits into this infrastructure perfectly. A lot of it is thanks to MS, but we haven’t had to spend money on retraining staff to integrate modern JS into our stack.
The language itself offers a lot of freedom, which includes risk, but I don’t personally think it’s worsening that regard than python.
> You don’t achieve acceptance without a level of respect that comes from being useful
But this is not the case of JS. JS is used because is the only posibility on web. Imagine if cobol was the only posible language to code on any desktop, and any other MUST transpile to it.
Just by force of necessity, Cobol will get nicer. But not because is respected, is because WHAT OTHER OPTION YOU HAVE?
JS eventually migrate to servers and get easier to integrate? Well that is the same with Lua and others that are even nicer.
I feel everyone ping-pongs between these ideas; I've been there and I'm no longer convinced the way you are. You can't take single threaded code and just make it multithreaded no matter how much time the compiler spends on it. And in a JIT environment, the compiler doesn't actually get much time to spend optimizing code.
You can't just take advantage of SIMD, hyperthreading, multicore, or large caches without writing code in a way that takes advantage of that even at a high level. Some things can be faster when compiled at runtime but everything is a trade off.
But ultimately I believe that C is fast mostly due to manual memory management but that's not a trade-off I'd take for most tasks.
I think his point is that if you write normal single threaded code then your other cores are going to be idle. But if you write in a high level JITed GCd language then the other cores will be busy making your main thread run faster by profiling it, recompiling it with risky optimisations, clearing garbage asynchronously and so on.
What do you mean by "multiple cores to optimise a single threaded piece of code?" This makes it sound like code can automatically become multithreaded which is not true (threading is one of JS's great weak points).
v8 is an amazing piece of engineering but it's not at the point where it allows application developers to take much advantage of SIMD, hyperthreading, or multicore.
SIMD, multicore, and caches don't just magically happen with better compilers. SIMD requires very specific memory access and computation patterns, and cache-friendly code has similar restrictions. The features of even basic javascript fly in the face of code being simd and cache friendly except for the most trivial programs.
Automagically parallelizing general serial code is something that isn't feasible on any hardware similar to modern cpus and probably will never mesh well with fast single-threaded performance (communication and synchronization in hardware is HARD)
I used to be a Smalltalk developer on Windows 3.1 for a brief time in the early 90's. When I found out that internally, Smalltalk was compiled into a sort of intermediate assembly language, and run on a virtual machine, I wondered why couldn't we just distribute the assembly language part over the Internet, where it could be run on any kind of machine with an interepreter. Everyone told me I was crazy, it was impractical, it would never work.
I used to work for a Smalltalk vendor. It was even better than that. For awhile we had the hottest JIT virtual machine. Smalltalks that followed the original design ran bit identically across multiple platforms. At one point, Squeak Smalltalk was running bit identically across 50 combinations of CPU and OS, and Squeak and its descendants probably can run bit identically across several times that number of environments now.
But it gets even crazier. (Non-Smalltalk) In the 90's, there was an OS entirely written in a virtual Instruction Set (TAOS) which was JIT assembled into real machine code as fast as it could be read off of disk and ran at 80-90% of native speeds. This OS could be ported by simply porting the assembler, which typically took about 3 days.
Back to Smalltalk craziness. There were also in-house research versions of Smalltalk that could prune their images as small as 45k, and were suitable for creating command line tool executables. As it was, VisualWorks, if you turned off things like the splash screen, actually could start faster than the Perl runtime in the late 90's, though you'd be hard pressed to create an image below 500kB. (Even getting it below a megabyte was an incredible feat.)
The tech industry could be way ahead of where it is now, if only everyone were like early adopters. The thing is, most people are quite different.
Keep in mind that a lot of performance differences come out of the snowball effect of popularity driving product development. X86 being faster than MIPS today has nothing to do with the relative merits of the ISAs and everything to do with the millions of people who wanted to run X86 code really fast for the past 25 years. JavaScript's foray into this scale of popularity is pretty recent, with the widespread adoption of "Web 2.0" and AJAX only dating back to about 2005 (both are older, but spent a while in the throes of obscurity and incompatibility).
But it's not c level performance. Throwing multiple cores at js to get c level performance glossession over an important detail: in C, those cores are free to do other things.
JITs aren't a panacea for everything. As the blog post said, if you want to avoid falling off a performance cliff you must write "CranskshaftScript" instead of Javascript. And the only way to be sure that the optimizer is content through a specialized profiler.
> but for 90% of use cases javascript actually makes sense and can be the most performant.
This is a weird definition of performant, using more resources to achieve the same result faster, it seems akin to saying JS is more performant than C if you buy a newer CPU to run the code on.
> But the more I've learnt about the benefits of "on the fly" / "just in time" optimising compilers, the more I'm convinced this is the future of computing
It's been the future of computing for 30 years, possibly longer.
A couple of the replies here seem to read that you've said something like "JIT magically makes single-threaded code multi-threaded." I'm a little mystified by the reading comprehension on HN...
You can write a multi-threaded optimizer that works on single-threaded code. Using multiple threads on multiple cores to optimize single-threaded code ... is pretty darn cool, but barbegal never said it made the single- into multi- ...
Algorithms take you far. Optimized code takes you the rest of the way. If you’re after performance you’ll do both, of course. I’ve found that the only subfield of which I know in which mathematicians still write their own C or C++ is concise/efficient data structures and algorithms.
Sure, you won't beat C for low memory devices or hardware that you have complete control over but for 90% of use cases javascript actually makes sense and can be the most performant.