Skip to content

Commit 0226741

Browse files
authored
ZJIT: Profile specific objects for invokeblock (#15051)
I made a special kind of `ProfiledType` that looks at specific objects, not just their classes/shapes (#15051). Then I profiled some of our benchmarks. For lobsters: ``` Top-6 invokeblock handler (100.0% of total 1,064,155): megamorphic: 494,931 (46.5%) monomorphic_iseq: 337,171 (31.7%) polymorphic: 113,381 (10.7%) monomorphic_ifunc: 52,260 ( 4.9%) monomorphic_other: 38,970 ( 3.7%) no_profiles: 27,442 ( 2.6%) ``` For railsbench: ``` Top-6 invokeblock handler (100.0% of total 2,529,104): monomorphic_iseq: 834,452 (33.0%) megamorphic: 818,347 (32.4%) polymorphic: 632,273 (25.0%) monomorphic_ifunc: 224,243 ( 8.9%) monomorphic_other: 19,595 ( 0.8%) no_profiles: 194 ( 0.0%) ``` For shipit: ``` Top-6 invokeblock handler (100.0% of total 2,104,148): megamorphic: 1,269,889 (60.4%) polymorphic: 411,475 (19.6%) no_profiles: 173,367 ( 8.2%) monomorphic_other: 118,619 ( 5.6%) monomorphic_iseq: 84,891 ( 4.0%) monomorphic_ifunc: 45,907 ( 2.2%) ``` Seems like a monomorphic case for a specific ISEQ actually isn't a bad way of going about this, at least to start...
1 parent 4f56abb commit 0226741

File tree

10 files changed

+124
-23
lines changed

10 files changed

+124
-23
lines changed

insns.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,7 @@ invokeblock
11371137
// attr bool handles_sp = true;
11381138
// attr rb_snum_t sp_inc = sp_inc_of_invokeblock(cd->ci);
11391139
// attr rb_snum_t comptime_sp_inc = sp_inc_of_invokeblock(ci);
1140+
// attr bool zjit_profile = true;
11401141
{
11411142
VALUE bh = VM_BLOCK_HANDLER_NONE;
11421143
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock);

vm_insnhelper.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5497,6 +5497,12 @@ vm_invoke_proc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
54975497
return vm_invoke_block(ec, reg_cfp, calling, ci, is_lambda, block_handler);
54985498
}
54995499

5500+
enum rb_block_handler_type
5501+
rb_vm_block_handler_type(VALUE block_handler)
5502+
{
5503+
return vm_block_handler_type(block_handler);
5504+
}
5505+
55005506
static inline VALUE
55015507
vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
55025508
struct rb_calling_info *calling, const struct rb_callinfo *ci,
@@ -6059,6 +6065,12 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv
60596065
}
60606066
}
60616067

6068+
VALUE
6069+
rb_vm_get_block_handler(rb_control_frame_t *reg_cfp)
6070+
{
6071+
return VM_CF_BLOCK_HANDLER(reg_cfp);
6072+
}
6073+
60626074
static VALUE
60636075
vm_invokeblock_i(struct rb_execution_context_struct *ec,
60646076
struct rb_control_frame_struct *reg_cfp,

zjit.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ rb_zjit_class_has_default_allocator(VALUE klass)
301301
return alloc == rb_class_allocate_instance;
302302
}
303303

304+
305+
VALUE rb_vm_get_block_handler(rb_control_frame_t *reg_cfp);
306+
enum rb_block_handler_type rb_vm_block_handler_type(VALUE block_handler);
307+
304308
// Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them.
305309
VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
306310
VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);

zjit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def stats_string
164164
print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20)
165165
print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20)
166166
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20)
167+
print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10)
167168

168169
# Show most popular unsupported call features. Because each call can
169170
# use multiple complex features, a decrease in this number does not

zjit/bindgen/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ fn main() {
399399
.allowlist_function("rb_yarv_str_eql_internal")
400400
.allowlist_function("rb_str_neq_internal")
401401
.allowlist_function("rb_yarv_ary_entry_internal")
402+
.allowlist_function("rb_vm_get_block_handler")
403+
.allowlist_function("rb_vm_block_handler_type")
402404
.allowlist_function("rb_FL_TEST")
403405
.allowlist_function("rb_FL_TEST_RAW")
404406
.allowlist_function("rb_RB_TYPE_P")

zjit/src/cruby_bindings.inc.rs

Lines changed: 31 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit/src/distribution.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,18 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
114114
self.kind == DistributionKind::Monomorphic
115115
}
116116

117+
pub fn is_polymorphic(&self) -> bool {
118+
self.kind == DistributionKind::Polymorphic
119+
}
120+
117121
pub fn is_skewed_polymorphic(&self) -> bool {
118122
self.kind == DistributionKind::SkewedPolymorphic
119123
}
120124

125+
pub fn is_megamorphic(&self) -> bool {
126+
self.kind == DistributionKind::Megamorphic
127+
}
128+
121129
pub fn is_skewed_megamorphic(&self) -> bool {
122130
self.kind == DistributionKind::SkewedMegamorphic
123131
}

zjit/src/hir.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4407,6 +4407,34 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
44074407
// profiled cfp->self.
44084408
if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable {
44094409
profiles.profile_self(&exit_state, self_param);
4410+
} else if opcode == YARVINSN_invokeblock || opcode == YARVINSN_trace_invokeblock {
4411+
if get_option!(stats) {
4412+
let iseq_insn_idx = exit_state.insn_idx;
4413+
if let Some(operand_types) = profiles.payload.profile.get_operand_types(iseq_insn_idx) {
4414+
if let [self_type_distribution] = &operand_types[..] {
4415+
let summary = TypeDistributionSummary::new(&self_type_distribution);
4416+
if summary.is_monomorphic() {
4417+
let obj = summary.bucket(0).class();
4418+
let bh_type = unsafe { rb_vm_block_handler_type(obj) };
4419+
if bh_type == block_handler_type_iseq {
4420+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_iseq));
4421+
} else if bh_type == block_handler_type_ifunc {
4422+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_ifunc));
4423+
} else {
4424+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_monomorphic_other));
4425+
}
4426+
} else if summary.is_skewed_polymorphic() || summary.is_polymorphic() {
4427+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_polymorphic));
4428+
} else if summary.is_skewed_megamorphic() || summary.is_megamorphic() {
4429+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_megamorphic));
4430+
} else {
4431+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_no_profiles));
4432+
}
4433+
} else {
4434+
fun.push_insn(block, Insn::IncrCounter(Counter::invokeblock_handler_no_profiles));
4435+
}
4436+
}
4437+
}
44104438
} else {
44114439
profiles.profile_stack(&exit_state);
44124440
}

zjit/src/profile.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ impl Profiler {
4343
fn peek_at_self(&self) -> VALUE {
4444
unsafe { rb_get_cfp_self(self.cfp) }
4545
}
46+
47+
fn peek_at_block_handler(&self) -> VALUE {
48+
unsafe { rb_vm_get_block_handler(self.cfp) }
49+
}
4650
}
4751

4852
/// API called from zjit_* instruction. opcode is the bare (non-zjit_*) instruction.
@@ -83,6 +87,7 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
8387
YARVINSN_opt_length => profile_operands(profiler, profile, 1),
8488
YARVINSN_opt_size => profile_operands(profiler, profile, 1),
8589
YARVINSN_opt_succ => profile_operands(profiler, profile, 1),
90+
YARVINSN_invokeblock => profile_block_handler(profiler, profile),
8691
YARVINSN_opt_send_without_block | YARVINSN_send => {
8792
let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr();
8893
let argc = unsafe { vm_ci_argc((*cd).ci) };
@@ -135,6 +140,17 @@ fn profile_self(profiler: &mut Profiler, profile: &mut IseqProfile) {
135140
types[0].observe(ty);
136141
}
137142

143+
fn profile_block_handler(profiler: &mut Profiler, profile: &mut IseqProfile) {
144+
let types = &mut profile.opnd_types[profiler.insn_idx];
145+
if types.is_empty() {
146+
types.resize(1, TypeDistribution::new());
147+
}
148+
let obj = profiler.peek_at_block_handler();
149+
let ty = ProfiledType::object(obj);
150+
unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) };
151+
types[0].observe(ty);
152+
}
153+
138154
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139155
pub struct Flags(u32);
140156

@@ -147,6 +163,8 @@ impl Flags {
147163
const IS_T_OBJECT: u32 = 1 << 2;
148164
/// Object is a struct with embedded fields
149165
const IS_STRUCT_EMBEDDED: u32 = 1 << 3;
166+
/// Set if the ProfiledType is used for profiling specific objects, not just classes/shapes
167+
const IS_OBJECT_PROFILING: u32 = 1 << 4;
150168

151169
pub fn none() -> Self { Self(Self::NONE) }
152170

@@ -155,6 +173,7 @@ impl Flags {
155173
pub fn is_embedded(self) -> bool { (self.0 & Self::IS_EMBEDDED) != 0 }
156174
pub fn is_t_object(self) -> bool { (self.0 & Self::IS_T_OBJECT) != 0 }
157175
pub fn is_struct_embedded(self) -> bool { (self.0 & Self::IS_STRUCT_EMBEDDED) != 0 }
176+
pub fn is_object_profiling(self) -> bool { (self.0 & Self::IS_OBJECT_PROFILING) != 0 }
158177
}
159178

160179
/// opt_send_without_block/opt_plus/... should store:
@@ -182,6 +201,14 @@ impl Default for ProfiledType {
182201
}
183202

184203
impl ProfiledType {
204+
/// Profile the object itself
205+
fn object(obj: VALUE) -> Self {
206+
let mut flags = Flags::none();
207+
flags.0 |= Flags::IS_OBJECT_PROFILING;
208+
Self { class: obj, shape: INVALID_SHAPE_ID, flags }
209+
}
210+
211+
/// Profile the class and shape of the given object
185212
fn new(obj: VALUE) -> Self {
186213
if obj == Qfalse {
187214
return Self { class: unsafe { rb_cFalseClass },
@@ -251,6 +278,9 @@ impl ProfiledType {
251278
}
252279

253280
pub fn is_string(&self) -> bool {
281+
if self.flags.is_object_profiling() {
282+
panic!("should not call is_string on object-profiled ProfiledType");
283+
}
254284
// Fast paths for immediates and exact-class
255285
if self.flags.is_immediate() {
256286
return false;

zjit/src/stats.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,13 @@ make_counters! {
287287
// The number of times we ran a dynamic check
288288
guard_type_count,
289289
guard_shape_count,
290+
291+
invokeblock_handler_monomorphic_iseq,
292+
invokeblock_handler_monomorphic_ifunc,
293+
invokeblock_handler_monomorphic_other,
294+
invokeblock_handler_polymorphic,
295+
invokeblock_handler_megamorphic,
296+
invokeblock_handler_no_profiles,
290297
}
291298

292299
/// Increase a counter by a specified amount

0 commit comments

Comments
 (0)