A beautiful summary of the field!
In other words, this VM is highly optimised for this benchmark — but no-one ever ran it long enough before to notice that performance gets much worse over time.
Perhaps, although more likely, they noticed; and then in accordance with tradition, promptly buried their heads in the sands.
---
Since this thread is likely to be read by people who care about unexplainable performance glitches, I'll highlight an otherwise inexplicable ~20% run-to-run performance drop that I recently saw an explanation for: https://www.realworldtech.com/forum/?threadid=179700&curpost...
There has been a lot of talk about whether AVX512 is of real world benefit. One big issue is that it's so power hungry that the entire core compensates by slowing down whenever 512-bit instructions are executed. What's not well known is that with an AVX512 capable processor running the common Ubuntu 16.04, you may well encounter this slowdown even if your program never uses AVX512.
More specifically, there is a problem with the way that some versions of glibc are saving and restoring the 512-bit ZMM registers. Even at program start-up, the upper 256-bits can be in a "dirty" state, which can cause the core to slow down to the "AVX512-light" speed on any SSE, AVX, AVX2, or FP operation. Patches for glibc were backported as far at 2.25, but Ubuntu hasn't included these fixes in their updates.
The bottom line is that if you are working on an AVX512 capable system, you should probably make sure that you understand this bug before publishing any benchmark results.
Without being an expert in virtualization or containerization, it seems pretty obvious to me that if a certain instruction causes thermal throttling, then that'll cause thermal throttling whether the instruction is issued from a host OS or a guest OS or for any other reason. Indeed, the more layers of abstraction involved, the likelier you are to hit the problem.
Is there a fundamental benefit I am missing?
Strange you think that, because mobile phones use a variety of processors and architectures, and virtually all the binaries running on them were shipped with a platform-agnostic bytecode. Not to mention all the Java servers out there, or cloud services like Azure and Google Cloud which host .NET VMs.
As for your question, JIT has a few advantages beyond portability:
1. The fact that the bytecode isn't tied to the machine means the CPU and architecture has more flexibility to change, introduce new registers, vector units, and so on, and the VM can take advantage of these features without needing recompilation and redistribution of every program.
2. The machinery permitting runtime compilation is typically used for other things by programmers, like manually dynamically compiling new program fragments based on user input which are more performance-optimal (think LINQ expression trees and other forms of quasiquotation/explicit staging).
3. Programs can benefit from progress in optimization algorithms without needing to statically recompile and redistribute every program.
4. Bytecode is typically much safer and easier to analyze for various safety and security properties, so they can be shipped to clients on-demand for special purposes. Like web assembly.
As you can see, the main benefits aren't tied to performance, but to portability, safety and flexibility. I think the usual focus on performance is a red herring.
I'd recharacterize the performance question - the question as I've observed it has been "can we get all of these nice things without sacrificing too much performance", where the bar for "too much" has continually moved.
From the 100x slower days of early TCL implementations, to 10x, now down to some small percentage overhead.
It's good that the bar keeps moving - it puts pressure on the JIT world to keep delivering. I don't have the time to write up a long post about my personal thoughts on the long-term future of JITs, but I can summarize it thus (I realize it's a bit of polemic perspective):
1. I believe JIT compilation will slowly become the norm for most program execution environments - even for static languages. Wasm is a kick in that direction, and more will follow.
2. One reason I'm excited about JITs slowly making their way into static languages is things like proper first-class, _highly performant_ implementations of higher-order parametric polymorphism. For a quick example, I am always slightly annoyed when Rust tells me that I can't take a reference to a trait because it's not an Object trait:
``` trait Foo { fn bar<A>(...); }
let foo: &Foo = ...; // Invalid. Foo is not an object trait. ```
The reason we don't get to have this very nice, wonderful, powerful thing is because we need to specialize ALL occurrences of `bar`. I want this feature in my life. I've butted my head too many times against it and had to implement a compromised solution with type erasure and re-discovery on the other end of an API, leading to lots of unnecessary complexity, code duplication, and surface area for bugs.
A VM carrying a JIT that knows how to compile new specializations on demand effectively renders the notion of `object traits` obsolete.
Now Rust is not the right language for that (it wants to be bare metal and runtime-free and low-level). However, I would love for higher-level statically typed languages to start offering me these capabilities.
3. I actually believe performance with JITs will eventually be better than pre-compiled code. Our techniques are still young and somewhat ad-hoc. There are concrete areas where there is significant room for improvement in the design and implementation of JIT engines. This industry is still basically in its infancy.
JIT has been the norm since they exist, code is compiled to bytecode and AOT compiled at installation time or on demand if the environment is changed in some way.
In a way, similar to deployment scenarios adopted by Android, Windows Store and to a smaller extent Apple devices.
But of course no JIT compiler ever is sufficiently smart and any execution path benefit gained is lost to poor cache discipline of VMs. And the whole JIT/hotspot concept was really a hindsight attempt to get away from abysmal performance of VM based languages.
Today, as we have both AOT and JIT compilers for Java, we can see that the JIT does, indeed, generate better code. By how much depends on your specific use-case.
> lost to poor cache discipline of VMs
What are you referring to? If you're talking about memory layout, then this has nothing to do with the VM and everything to do with the language design. The reason Java has little control over memory layout was that it didn't make much of a difference in the '90s; now it does, so Java is getting control of layout.
Java now offers "deploy-time" AOT compilation, and the results are not as good as a JIT (YMMV).
It has been available since the early days in commercial JDKs and initially it was the deployment option on IBM mainframes, by converting JVM bytecodes into the bytecodes of the mainframe execution environments.
E.g. when you compile an application C-style, you set the target architecture to the lowest CPU you want to support. If this is , say, SSE, then no SSE2 instructions will be used by the program and the sometimes huge performance gains from them are lost.
A VM like hotspot can generate code for this specific machine. If you have SSE2, it will generate SSE2 instructions. If you dont, it wont.
Recently, I see more applications choosing code paths based on CPUID, but this only works for CPUs existing at compile time. If you app was compiled in 2016, in wont use CPU features from 2018. If your java app from 2016 runs on a JRE from 2018, it will use them.
Another thing is these kind of gains with instruction level efficiencies are exceedingly rare and your typical VM program never runs any code where SIMD extensions would make a difference.
https://nullprogram.com/blog/2018/05/27/
Java uses the same technique.
Basically, a JIT allows you to skip the overhead of the GOT.
Devirtualization is another case.
I have personally seen Java beating an existing C++ program for the reason I describe, but I cant publish the results here. You have to program carefully or you loose all speed benefits because of pointer chasing. But it is possible.
This is a problem that is GNU/Linux-specific. Windows DLLs are not PIC (position-independent code), so they do not suffer from this problem and don't need a GOT.
See
> https://www.symantec.com/connect/articles/dynamic-linking-li...
for details.
The method that Windows uses is much faster in the average case, but slower in the "bad" case. So GNU/Linux vs Windows have different performance tradeoffs.
Profile-guided, whole-program optimization can do that for whatever execution patterns the developer feeds them. It will be faster than the JIT's because it has more time to analyze and optimize. The JIT will be better on the exceptional cases the AOT compiler didn't optimize for. In many apps, I'd much rather have the AOT approach than the JIT approach. Heck, some routines in fast path might even get superoptimized if developer wants. :)
It also allows the .Net regular expression library to be able to dynamically generate IL-code to match a given regular expression, which is then dynamically loaded into the process and JIT compiled to machine code. Other projects surely use the same facilities to dynamically generate and run specialized code based on user input and/or environment.
That's not true — Everybody actually uses it in practice. The .jar you grab from Maven Central just works unconditionally, your CI server builds one single artefact that runs on your Mac and on the Linux server.
People often aren't aware of this because the actual mechanisms tend to be hidden inside libraries or tools they depend on.
Can you provide any example? I can't think of any and it sounds very interesting since you seem to be implying that it's common. I assume that "dynamic loading" means something more sophisticated than loading a dynamic library at run-time?
And even "just" dynamically loading a library requires recompilation because it will invalidate optimizer assumptions such as devirtualization (part of class hierarchy analysis in hotspot).
Of course, I could run the applications on the Linux servers and remote debug, but the round trip time for rebooting and getting back to the same breakpoint is longer with more manual interaction.
Um, no. The reality is that almost everyone uses it. It's extremely common for developers to develop on Windows or Mac and deploy on Linux.
On the contrary - for example, lots of people develop on MacOS and deploy to Linux.
I think for me, the main takeaway from article is startup time for JITs is irrelevant, since it does not affect performance much.
First, the JIT compiler has often done most of its work during the first in-process iteration, so the plots are not generally following a "one in-process iteration is slow, then the JIT kicks in from in-process iteration 2 onwards" model. Put another way, the JIT compiler is often making even the first in-process iteration run pretty fast.
Second, I would suggest that the often small timing differences are more worrying than they may first appear. In a semi-mature compiler, optimisations are frequently in the range of a 0.5-1% improvement. So if your measurements are only accurate to (say) 2%, most of your attempted optimisations will be misclassified (bad optimisations will sometimes be measured as good; good optimisations will sometimes be measured as bad). Furthermore, when a VM recompiles things, it generally performs several optimisations at once. Thus, if the overall performance gets worse (even if by a small bit), it may suggest that several optimisations performed badly at the same time.
The idea about some runs having slowdowns might not be significant if it's just one run out of 30 that does it though. The result in the last Diagram of TruffleRuby on spectralnorm having 25 slowdowns and 5 no steady-state is interesting.
Full paper at : http://soft-dev.org/pubs/html/barrett_bolz-tereick_killick_m...
What do people use to make their computers stable for benchmarking?
Can you link to any resources that discuss this?