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.
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.
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.
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.
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.
>> 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.
Ironically it is now used as the language to beat.
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.
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).
Pretty cool that it can run faster than AOT compiled C, any benchmarks or posts with more details on this?
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.
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.
More in my article "Jitterdämmerung": http://blog.metaobject.com/2015/10/jitterdammerung.html
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.
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.
Oh well this VM meme is getting old,
https://www.ptc.com/en/products/developer-tools/perc
https://docs.microsoft.com/en-us/dotnet/framework/net-native...
> 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.
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.
It's a rigid term:
https://en.wikipedia.org/wiki/Tracing_garbage_collection#Pre...
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.
Ah, neat! I learned something. :)
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.
Structure could be stack allocated, but any of it's fields would not if there is anything but a number.
A trivial example:
https://segment.com/blog/allocation-efficiency-in-high-perfo...
func main() {
x := 42
fmt.Println(x)
}
./main.go:7: x escapes to heap
So a trivial interface cast leads to allocation. BenchmarkEscapeInterface-4 50000000 33.3 ns/op 8 B/op 1 allocs/op
BenchmarkEscapeConcreteValue-4 200000000 9.45 ns/op 0 B/op 0 allocs/op
BenchmarkEscapeConcretePointer-4 100000000 10.0 ns/op 0 B/op 0 allocs/op
But arrays are stack allocated: BenchmarkEscapeArray-4 50000000 21.3 ns/op 0 B/op 0 allocs/op
And structs are stack allocated, as are their fields--even fields that are structs, slices, and strings!: BenchmarkEscapeStruct-4 100000000 12.8 ns/op 0 B/op 0 allocs/op
The code: type Inner struct {
Slice []int
String string
Int int
}
type Struct struct {
Int int
String string
Nested Inner
}
func (s Struct) AddThings() int {
return s.Int + len(s.String) + len(s.Nested.Slice) + len(s.Nested.String) +
s.Nested.Int
}
func BenchmarkEscapeStruct(b *testing.B) {
for i := 0; i < b.N; i++ {
s := Struct{
Int: 42,
String: "Hello",
Nested: Inner{
Slice: []int{0, 1, 2},
String: "World!",
Int: 42,
},
}
_ = s.AddThings()
}
}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 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.
> 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.
https://days2011.scala-lang.org/sites/days2011/files/ws3-1-H...
If you add in cases where simd, software prefetching, or memory access batching help, the difference will only grow.
They found that most programs ran slightly faster in the JIT. I'm still not sure why this technique didn't catch on.
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.
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.
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.
Not much respect, but more acceptance as a unavoidable language to use that thanks of millons of $$$$/human-hours of effort to polish it... is ok-ish.
This is how people end up using it on the back-end when they do have a choice.
Though "respect" is a corny word to use for any language. I don't respect my screwdriver either.
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.
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.
But anyway. JS win for market brute force.
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.
It's not that performant either, it's just that it's getting closer to Java-class performance which is enough for many types of workloads.
JS is living proof that throwing enough money at a problem can go quite a long way.
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.
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.
I think he means this is done behind the scenes without your knowledge (some compiler/runtime magic), not some interface you can take advantage of.
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)
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.
As you say, I really wish tech had followed that route, think of all the cool things we'd have.
http://www.modulaware.com/mdlt69.htm
It's one of those alternate histories for Web development I wish took off.
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.
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.
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- ...
You can't with Javascript. You need concurrency-friendly programming model and code structure for compiler to be able to do anything that fancy.