4 ms·
Here's the summary: Ruby like most of all other Linux applications from that age was written to run in multi-process mode for parallelism, instead of being mult
by habibur 5y ago
Here's the summary: Ruby like most of all other Linux applications from that age was written to run in multi-process mode for parallelism, instead of being multi threaded.
But running multi-process is heavy on the JVM as it has to load the virtual machine and JIT for every process start, special when the processes are short lived.
Java solves it by encouraging running things as multi-threaded instead of multi-processed.
But Ruby wasn't built to run multi-threaded. And CRuby even if can run threads, does it by implementing a global thread lock. Aka, only one thread can run in a process at a time. The other threads have to wait until it finishes.
It looks like the multi-process implementation is good enough for CRuby and JRuby's attempt to turn it into multi threaded application didn't improve things.
Solution is to build a JVM that can load the virtual machine, JIT and execute applications fast enough like C programs.
- pjmlp 5y agoWhat makes GraalVM interesting is that it builds upon the research done in JikesRVM and MaximeVM, alongside a free beer offering. Commercial versions of "a JVM that can load the virtual machine, JIT and execute applications fast enough like C programs" have been available since around 2000, like Excelsior JET or WebSphere Real Time JVM among others. The JIT cache used in recent versions of Hotspot started as part of JRockit JVM, also commercial only product.
- dragonwriter 5y ago> Ruby like most of all other Linux applications from that age was written to run in multi-process mode for parallelism, instead of being multi threaded. That’s...not really true. Ruby wasn’t really written with parallelism in mind at all, because it mostly ran on machines that couldn’t run really parallel processes. MRI threads were originally green threads, which allow a high degree of concurrency without parallelism, with less overhead than native threads. When old MRI was replaced with YARV in 1.9 (which became the new MRI), it got native threads with a global VM lock (GVL, similar idea to Python’s GIL) which allowed running thread-safe native code with real parallelism but only having one thread running Ruby code at a time. This made Ruby thread-based concurrency somewhat more expensive, but some parallelism possible in Ruby (as native code can and some basic common processes like waiting on I/O do release the GVL.) And Ruby 3.0 introduces a new parallelism model with Ractors (basically inspired by the Actor model), which are logically above the thread level (each contains its own set of nonshared threads) and below the process, don’t share mutable state within the VM, and each have their own VM lock, allowing a higher degree of Ruby parallelism without going multiprocess.
- ZeroGravitas 5y agoI think I preferred the author's own summary: > I think there are two reasons for this: > * Real-World projects like Jekyll involve a lot more code, and JITing that code has a high start-up cost. > * Real-world code like Jekyll or Rails is optimized for MRI Ruby, and many of those optimizations don’t help or actively hinder the JVM. The title seems a bit provocative, though I guess if you were reading it from within the Jekyll community, it makes sense without further disclaimers, but otherwise the article seems fairly even-handed. The threading stuff just seems like a special case of his second point.
- tom_mellior 5y ago> Here's the summary: [...] But running multi-process is heavy on the JVM as it has to load the virtual machine and JIT for every process start, special when the processes are short lived. This is a valid summary of some facts, but not of the article or facts relevant to the article. Some quotes: "Jekyll is not forking processes, so that is not the issue." "The area where JRuby and TruffleRuby shine are long running processes that have had time to warm up. Based on suggestions I put together a repo of a simple small Jekyll build being built 20 times by the same process in a repo here. After 20 builds with the same running process the build times do start to converge, but even after that MRI Ruby is still fastest."
- saurik 5y agoThe way Android solved this problem (before they implemented AOT) was to fork the JVM post-load (and I think even JIT) of all the core Java and Android classes; the real somewhat-fundamental problem with the JVM in a multi-process system isn't really having to "load the virtual machine and JIT for every process start" but the multiple, almost-certainly-uncoordinated garbage collectors. http://www.cydiasubstrate.com/id/727f62ed-69d3-4956-86b2-bc0ffea110c1/ http://www.cydiasubstrate.com/id/727f62ed-69d3-4956-86b2-bc0...
- pjmlp 5y agoThey still use zygote, however as expected, cheap Android devices aren't the best hardware to run an AOT compiler, so with Android 7 they introduced a multitier execution, with interpreter hand written in Assembly, JIT compiler and AOT that take advantage of PGO data. The AOT compiler only runs when the device is idle. Starting with Android 10, they introduced a mechanism to upload PGO data into the store, so that when an APK is installed, if such data is already available the JIT/AOT don't have to relearn everything from scratch regarding the application. https://source.android.com/devices/tech/dalvik/jit-compiler https://source.android.com/devices/tech/dalvik/jit-compiler They also improved the GC several times, the latest generation is quite good, https://source.android.com/devices/tech/dalvik/gc-debug https://source.android.com/devices/tech/dalvik/gc-debug There are several Google IO talks about how they went through this.
- vrotaru 5y agoIBM J9 has something called the JIT server. My understanding that JIT is done is one process and then provided as a service to clients (other processes) Never used it though, so no idea how it works in practice.
- pjmlp 5y agoIt shares PGO data across the cluster so that all JIT clients eventually drive to an optimal set of common optimizations. As it runs standalone it can also take advantage of the whole server for itself for more resource hungry optimizations without impacting running code. The generated code is then reflected back into the client JVM instances.