6 ms·
Can someone explain, is YJIT being abandoned over the new ZJIT? [0] And if so, will these YJIT features likes Fast Allocations be brought to ZJIT? https://rai
by alberth 1y ago
Can someone explain, is YJIT being abandoned over the new ZJIT? [0]
And if so, will these YJIT features likes Fast Allocations be brought to ZJIT?
https://railsatscale.com/2025-05-14-merge-zjit/ https://railsatscale.com/2025-05-14-merge-zjit/
- firemelt 1y agoafter reading your source I'd say YJIT still there up until ZJIT is ready and on par with YJIT and the features is there when its there
- ksec 1y ago>For this reason, we will continue maintaining YJIT for now and Ruby 3.5 will ship with both YJIT and ZJIT. In parallel, we will improve ZJIT until it is on par (features and performance) with YJIT. I guess YJIT will always be faster in warmup and minimal increase of memory usage. ZJIT being more traditional should bring more speedup than YJIT. But most of the speedup right now is still coming from rewriting C into Ruby.
- uticus 1y ago> But most of the speedup right now is still coming from rewriting C into Ruby. Quick glance, this statement seems backwards - shouldn't C always be faster? or maybe i'm misunderstanding how the JIT truly works
- vidarh 1y agoUnless your JIT can analyse the full code, a transition between byte code and native code is often costly because the JIT won't be able to optimize the full path. Once your JIT generates good enough code, it then becomes faster to avoid that transition even in cases when in isolation native code might still be faster. EDIT: Note that this isn't an inherent limit. You could write a JIT that could analyze the compiled C code too. It's just that it's much harder to do.
- ksec 1y agoAnd that is what TruffleRuby did. I had wished there is a subset of Ruby that could be compiled to C. And then all gems should be written in that instead. I remember a few people tried but failed though. Have to dig up the old HN threads again.
- vidarh 1y agoCompiling a subset of Ruby to C wouldn't be that hard, but making it compile to C that is fast enough to be worth it is. Not because the Ruby VM is particularly fast, but because the "naive" way of compiling Ruby to C still incurs almost all of the overhead. E.g. TruffleRuby is fast in part because it will do things like try to avoid method calls for built in types where the standard operations haven't been overridden, but that requires a lot of extra machinery... So I'm not sure how much compiling to C would help for gems that use C to speed things up. I think maybe an easier target would be to compile C to a slightly augmented Ruby bytecode. If you control the C compiler you could do things like make C code follow the Ruby calling convention until/unless calling external C code, and avoid a lot of stack overhead.
- pjmlp 1y agoNot everyone failed, see RubyMotion. However they decided it was more useful as a commercial product.
- molf 1y agoC itself is fast; it's calls to C from Ruby that are slow. [1] Crossing the Ruby -> C boundary means that a JIT compiler cannot optimize the code as much; because it cannot alter or inline the C code methods. Counterintuitively this means that rewriting (certain?) built-in methods in Ruby leads to performance gains when using YJIT. [2] [1]: https://railsatscale.com/2023-08-29-ruby-outperforms-c/ https://railsatscale.com/2023-08-29-ruby-outperforms-c/ [2]: https://jpcamara.com/2024/12/01/speeding-up-ruby.html https://jpcamara.com/2024/12/01/speeding-up-ruby.html
- nightpool 1y agoThe sibling comments mention that C is used in a lot of places in Ruby that incur cross-language overheads, which is true, but it's also just true that in general, even ignoring this overhead, JIT'd functions are going to be faster then their comparable C functions, because 1) they have more profiling information to be able to work from, 2) they have more type information, and (as a consequence of 1&2) 3) they're more likely to be monomorphized, and the compiler is more able to inline specialized variants of them into different chunks of the code. Among other optimizations!
- uticus 1y ago> ...they have more profiling information to be able to work from... more type information... more likely to be monomorphized, and the compiler is more able to inline specialized variants of them into different chunks of the code. this is fascinating to me. i always assumed C had everything in the language that was needed for the compiler to use. in other words, the compiler may have a lot to work through, but the pieces are all available. but this makes it sound like JIT'd functions provide more info to the compiler (more pieces to work with). is there another language besides C that does have language features to indicate to the compiler how to make things as performant as possible?
- foobazgt 1y agoJITs have runtime information that static compilers do not. Sometimes that's not a huge benefit, but it can often have massive performance implications. For example, a JIT can inline dynamically loaded code into your own code. That sounds unusual, but it's actually ultra-common in practice. For example, this shows up in something as mundane and simple as configurable logging.
- dhruvrajvanshi 1y agoA very simple way to think about is that if an intrinsic is written in C, the JIT can't easily inline it, whereas all ruby code can be inlined. Inlining is the most important optimization that enables other optimizations. It's not necessarily the fact that C doesn't have enough information, it's just that the JIT can reason about Ruby code better than it can about C code. To the JIT, C code is just some function which does things and the only thing it can do with it is to call it. On the other hand, a Ruby function's bytecode is available to the jit, so if it sees fit, it can copy paste the function body into the call site and eliminiate the function call overhead. Further, after the inlining, it can apply a lot of further optimizations across what was previously a function boundary. In theory, you could have a way to "compile" the C intrinsics into the JIT's IR directly and that would also give you similar results.
- tenderlove 1y agoIt's not being abandoned, we're just shifting focus to evaluate a new style of compiler. YJIT will still get bug fixes and performance improvements. ZJIT is a method based JIT (the type of compiler traditionally taught in schools) where YJIT is a lazy basic block versioning (LBBV) compiler. We're using what we learned developing and deploying YJIT to build an even better JIT compiler. IOW we're going to fold some of YJIT's techniques in to ZJIT. > And if so, will these YJIT features likes Fast Allocations be brought to ZJIT? It may not have been clear from the post, but this fast allocation strategy is actually implemented in the byte code interpreter. You will get a speedup without using any JIT compiler. We've already ported this fast-path to YJIT and are in the midst of implementing it in ZJIT.
- FooBarWidget 1y agoWhy is a traditional method based JIT better than an LBBV JIT? I thought YJIT is LBBV because it's a better fit for Ruby, whereas traditional method based JIT is more suitable for static languages like Java.
- tenderlove 1y agoOne reason is that we think we can make better use of registers. Since LBBV doesn't "see" all blocks in a particular method all at once, it's much more challenging to optimize register use across basic blocks. We've added type profiling, so ZJIT can "learn" types from the runtime.
- whizzter 1y agoAre you piggybacking on LLVM for registerallocation and things like that or building from scracth? I can imagine combining BBV type branching info with register tracing would add a lot of complicated structures, but I thought the BB's of BBV was more or less analogous to SSA blocks so no middle ground to be found? (consdering if there are many megamorphic sites in the standard library?)
- deleted 1y ago[deleted]
- nightpool 1y agoIt doesn't sound like YJIT is being abandoned at all. Reading between the lines, it sounds like they want to invest most of their new development right now into a less-experimental architecture that's closer to other JITs and is easier to develop, but that they consider this a somewhat risky endeavor and aren't sure whether this investment is going to pan out in the long run. So they're going to try ZJIT out, but YJIT and the ideas behind it are by no means abandoned. They're just taking a pause to see if a rewrite will make it easier to maintain or produce better results in the long term.
- whizzter 1y agoYjit is based on Maxime's basic block vesioning PHD work (a JS JIT), the approach both has taken is very dynamic-type-focused, it's a clever way to basically propagate type-info as the code is built. The main benefit is that you get a sane JIT fairly quickly and it should behave well in most dynamic typing scenarios. They're pivoting (successfully?) to a more traditional way, letting the interpreter first profile the code (to figure out the types) and THEN produce entire methods with heavier optimizations that can do better register allocation. The BBV approach is sane out of the box but kinda unfamiliar for many compiler writers (problems hiring?) and probably has some performance ceilings without much complexity. The major question as to what method will win out depends on this question, how "monomorphic" or "polymorphic" is Ruby code in real life? Monomorphic basically means that only one "real type"(from the compilers point of view) will ever pass a codepath (and thus extra machinery to allow multiple types won't bring much benefit).