NOW LET US – AI RAG SaaS Studio TP.HCM
NOW LET US
Digital Product Studio
Back to news
DEV-TOOLS...5 min read

ZJIT removes redundant object loads and stores

Share
NOW LET US Article – ZJIT removes redundant object loads and stores

ZJIT introduces a new load-store optimization pass in its High-level Intermediate Representation (HIR), allowing it to outperform YJIT in specific benchmarks by eliminating redundant object field operations.

Intro

Since the post at the end of last year, ZJIT has grown and changed in some exciting ways. This is the story of how a new, self-contained optimization pass causes ZJIT performance to surpass YJIT on an interesting microbenchmark. It has been 10 months since ZJIT was merged into Ruby, and we’re now beginning to see the design differences between YJIT and ZJIT manifest themselves in performance divergences. In this post, we will explore the details of one new optimization in ZJIT called load-store optimization. This implementation is part of ZJIT’s optimizer in HIR. Recall that the structure of ZJIT looks roughly like the following.

flowchart LR
A(["Ruby"])
A --> B(["YARV"])
B --> C(["HIR"])
C --> D(["LIR"])
D --> E(["Assembly"])

This post will focus on optimization passes in HIR, or “High-level” Intermediate Representation. At the HIR level, we have two capabilities that are distinct from other compilation stages. Our optimizations in HIR typically utilize the benefits of our SSA representation in addition to the HIR instruction effect system.

These are the current analysis passes in ZJIT without load-store optimization, as well as the order in which the passes are executed.

run_pass!(type_specialize);
run_pass!(inline);
run_pass!(optimize_getivar);
run_pass!(optimize_c_calls);
run_pass!(fold_constants);
run_pass!(clean_cfg);
run_pass!(remove_redundant_patch_points);
run_pass!(eliminate_dead_code);

Here’s where load-store optimization gets added.

run_pass!(type_specialize);
run_pass!(inline);
run_pass!(optimize_getivar);
run_pass!(optimize_c_calls);
+ run_pass!(optimize_load_store);
run_pass!(fold_constants);
run_pass!(clean_cfg);
run_pass!(remove_redundant_patch_points);
run_pass!(eliminate_dead_code);

Overview

Ruby is an object-oriented programming language, so CRuby needs to have some notion of object loads, modifications, and stores. In fact, this is a topic already covered by another Rails at Scale blog post. The shape system provides performance improvements in CRuby (both interpreter and JIT), but there is still plenty of opportunity to improve JIT performance. Sometimes optimizing interpreter opcodes one at a time leaves repeated loads or stores that can be cleaned up with a program analysis optimization pass. Before getting into the weeds about this pass, let’s talk performance.

Results

The setivar benchmark for ZJIT changes dramatically on 2026-03-06. This is when load-store optimization landed in ZJIT. At the time of this writing, ZJIT takes an average of 2ms per iteration on this benchmark, while YJIT takes an average of 5ms.

This is the second time that ZJIT has clearly surpassed YJIT. At a high level, this means that ZJIT is over twice as fast as YJIT for repeated instance variable assignment, and more than 25 times faster than the interpreter!

A Troubling Development

However, there’s an important question we have to address - why should an optimization pass for object loads and stores have anything to do with instance variable assignment? It turns out that ZJIT’s High Intermediate Representation (HIR) uses LoadField and StoreField instructions both for both object instance variables, and for object shapes. We’re going to have to dig deeper into CRuby shapes and ZJIT HIR internals in order to make sense of this.

Background

So far, we’ve learned that HIR has LoadField and StoreField instructions. We’ve claimed that they are multi-purpose and that the performance wins come from optimizing object shapes, but that they can also apply to object instance variables. Because the algorithm works just as well for both situations, the rest of this post will focus on object instance variables. This allows us to demonstrate concepts in pure Ruby to make things more approachable.

Example

Let’s start with a simple example we can all agree on. Clearly this code snippet has a double store, and we can safely remove one of the @a = value calls.

class C
def initialize
value = 1
@a = value
@a = value
end
end

Here’s the same code snippet with an example of the call we remove. Here, we have elided a redundant StoreField instruction.

class C
def initialize
value = 1
@a = value
- @a = value
end
end

When should we remove LoadField and StoreField instructions? The HIR code snippets will come later. For now, we only need to know the mapping between Ruby and HIR for instance variable loads and stores.

| Ruby | HIR | |---|---| @var = value | StoreField var, @obj@offset, value | @var | LoadField var, @obj@offset |

Note: In a class’s initialize method, instance variable operations are likely to cause LoadField and StoreField instructions due to shape transitions. Outside of an initialize method, the loads and stores are more likely to be related to the instance variables themselves.

Cases

Let’s consider every edge case for our algorithm through short Ruby snippets to illustrate scenarios where we can and cannot elide LoadField or StoreField HIR instructions.

Redundant Store
class C
def initialize
value = 1
@a = value
# This store is redundant and should be elided in HIR
@a = value
end
end
Redundant Load
class C
def initialize
value = 1
@a = value
# We already know that this load is `value` and should be replaced
@a
end
end
Redundant Store with Aliasing
class C
attr_accessor :a
def initialize(value)
@a = value
end
end
class D
attr_accessor :a
def initialize(value)
@a = value
end
end
def multi_object_test
x = C.new(1)
y = D.new(1)
new_x_val = 2
new_y_val = 3
x.a = new_x_val
y.a = new_y_val
# We would like to elide this (but currently do not)
x.a = new_x_val
end

With variables pointing to distinct objects, we could elide the second store to object x. This is not currently implemented, but is a possible improvement with a technique called type-based alias analysis.

Required Store with Aliasing
class C
attr_accessor :a
def initialize(value)
@a = value
end
end
def multi_object_test
x = C.new(1)
y = x
new_x_val = 2
new_y_val = 3
x.a = new_x_val
y.a = new_y_val
# We should not elide the second `x.a` assignment because the `y.a` assignment modifies `x`
x.a = new_x_val
end
Required Store with Effects
def scary_method(obj)
obj.a = "Modified"
end
class C
attr_accessor :a
def initialize(value)
@a = value
end
end
def effectful_operations_between_stores_test
x = C.new(1)
x.a = 5
scary_method(x)
# We want to elide this but `scary_method` can modify `x`
x.a = 5
end

The Algorithm

Key Id

© 2026 Now Let Us. All rights reserved.

Source: Hacker News

Advertisement
Ad slot ready: 5887729102

More in this category

EXPLORE TOPICS

Discover All Categories

Deep dive into the specific technology sectors that matter most to you.