Skip to content

Commit ce4b1b6

Browse files
committed
feat(native): cache ruby ids for tracer
1 parent f03bf7d commit ce4b1b6

File tree

3 files changed

+90
-45
lines changed

3 files changed

+90
-45
lines changed

.agents/codebase-insights.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
flake.nix defines a pre-commit check using git-hooks.nix. Run 'nix develop' to install git hooks.
2+
3+
The native tracer (Rust extension) caches frequently used Ruby method IDs and
4+
class constants in the `Recorder` struct. When adding new Ruby method calls,
5+
ensure the IDs are interned once during allocation and stored in the struct for
6+
reuse.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Read the code of the native tracer and optimize inefficiencies, compute values once where possible, ensure tests pass

gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ struct Recorder {
5050
inst_meth_id: ID,
5151
parameters_id: ID,
5252
class_id: ID,
53+
to_a_id: ID,
54+
begin_id: ID,
55+
end_id: ID,
56+
to_i_id: ID,
57+
nsec_id: ID,
58+
source_id: ID,
59+
options_id: ID,
60+
members_id: ID,
61+
values_id: ID,
62+
to_h_id: ID,
63+
instance_variables_id: ID,
64+
instance_variable_get_id: ID,
65+
set_const_id: ID,
66+
open_struct_const_id: ID,
67+
set_class: VALUE,
68+
open_struct_class: VALUE,
5369
struct_type_versions: HashMap<String, usize>,
5470
}
5571

@@ -167,6 +183,20 @@ unsafe extern "C" fn ruby_recorder_alloc(klass: VALUE) -> VALUE {
167183
let inst_meth_id = rb_intern(b"instance_method\0".as_ptr() as *const c_char);
168184
let parameters_id = rb_intern(b"parameters\0".as_ptr() as *const c_char);
169185
let class_id = rb_intern(b"class\0".as_ptr() as *const c_char);
186+
let to_a_id = rb_intern(b"to_a\0".as_ptr() as *const c_char);
187+
let begin_id = rb_intern(b"begin\0".as_ptr() as *const c_char);
188+
let end_id = rb_intern(b"end\0".as_ptr() as *const c_char);
189+
let to_i_id = rb_intern(b"to_i\0".as_ptr() as *const c_char);
190+
let nsec_id = rb_intern(b"nsec\0".as_ptr() as *const c_char);
191+
let source_id = rb_intern(b"source\0".as_ptr() as *const c_char);
192+
let options_id = rb_intern(b"options\0".as_ptr() as *const c_char);
193+
let members_id = rb_intern(b"members\0".as_ptr() as *const c_char);
194+
let values_id = rb_intern(b"values\0".as_ptr() as *const c_char);
195+
let to_h_id = rb_intern(b"to_h\0".as_ptr() as *const c_char);
196+
let instance_variables_id = rb_intern(b"instance_variables\0".as_ptr() as *const c_char);
197+
let instance_variable_get_id = rb_intern(b"instance_variable_get\0".as_ptr() as *const c_char);
198+
let set_const_id = rb_intern(b"Set\0".as_ptr() as *const c_char);
199+
let open_struct_const_id = rb_intern(b"OpenStruct\0".as_ptr() as *const c_char);
170200
let recorder = Box::new(Recorder {
171201
tracer,
172202
active: false,
@@ -176,6 +206,22 @@ unsafe extern "C" fn ruby_recorder_alloc(klass: VALUE) -> VALUE {
176206
inst_meth_id,
177207
parameters_id,
178208
class_id,
209+
to_a_id,
210+
begin_id,
211+
end_id,
212+
to_i_id,
213+
nsec_id,
214+
source_id,
215+
options_id,
216+
members_id,
217+
values_id,
218+
to_h_id,
219+
instance_variables_id,
220+
instance_variable_get_id,
221+
set_const_id,
222+
open_struct_const_id,
223+
set_class: Qnil.into(),
224+
open_struct_class: Qnil.into(),
179225
struct_type_versions: HashMap::new(),
180226
});
181227
let ty = std::ptr::addr_of!(RECORDER_TYPE) as *const rb_data_type_t;
@@ -333,7 +379,7 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
333379
};
334380
}
335381
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_HASH) {
336-
let pairs = rb_funcall(val, rb_intern(b"to_a\0".as_ptr() as *const c_char), 0);
382+
let pairs = rb_funcall(val, recorder.to_a_id, 0);
337383
let len = RARRAY_LEN(pairs) as usize;
338384
let ptr = RARRAY_CONST_PTR(pairs);
339385
let mut elements = Vec::new();
@@ -362,8 +408,8 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
362408
};
363409
}
364410
if rb_obj_is_kind_of(val, rb_cRange) != 0 {
365-
let begin_val = rb_funcall(val, rb_intern(b"begin\0".as_ptr() as *const c_char), 0);
366-
let end_val = rb_funcall(val, rb_intern(b"end\0".as_ptr() as *const c_char), 0);
411+
let begin_val = rb_funcall(val, recorder.begin_id, 0);
412+
let end_val = rb_funcall(val, recorder.end_id, 0);
367413
return struct_value(
368414
recorder,
369415
"Range",
@@ -373,31 +419,32 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
373419
to_s_id,
374420
);
375421
}
376-
let set_id = rb_intern(b"Set\0".as_ptr() as *const c_char);
377-
if rb_const_defined(rb_cObject, set_id) != 0 {
378-
let set_cls = rb_const_get(rb_cObject, set_id);
379-
if rb_obj_is_kind_of(val, set_cls) != 0 {
380-
let arr = rb_funcall(val, rb_intern(b"to_a\0".as_ptr() as *const c_char), 0);
381-
if RB_TYPE_P(arr, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
382-
let len = RARRAY_LEN(arr) as usize;
383-
let ptr = RARRAY_CONST_PTR(arr);
384-
let mut elements = Vec::new();
385-
for i in 0..len {
386-
let elem = *ptr.add(i);
387-
elements.push(to_value(recorder, elem, depth - 1, to_s_id));
388-
}
389-
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Set");
390-
return ValueRecord::Sequence {
391-
elements,
392-
is_slice: false,
393-
type_id,
394-
};
422+
if NIL_P(recorder.set_class) {
423+
if rb_const_defined(rb_cObject, recorder.set_const_id) != 0 {
424+
recorder.set_class = rb_const_get(rb_cObject, recorder.set_const_id);
425+
}
426+
}
427+
if !NIL_P(recorder.set_class) && rb_obj_is_kind_of(val, recorder.set_class) != 0 {
428+
let arr = rb_funcall(val, recorder.to_a_id, 0);
429+
if RB_TYPE_P(arr, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
430+
let len = RARRAY_LEN(arr) as usize;
431+
let ptr = RARRAY_CONST_PTR(arr);
432+
let mut elements = Vec::new();
433+
for i in 0..len {
434+
let elem = *ptr.add(i);
435+
elements.push(to_value(recorder, elem, depth - 1, to_s_id));
395436
}
437+
let type_id = recorder.tracer.ensure_type_id(TypeKind::Seq, "Set");
438+
return ValueRecord::Sequence {
439+
elements,
440+
is_slice: false,
441+
type_id,
442+
};
396443
}
397444
}
398445
if rb_obj_is_kind_of(val, rb_cTime) != 0 {
399-
let sec = rb_funcall(val, rb_intern(b"to_i\0".as_ptr() as *const c_char), 0);
400-
let nsec = rb_funcall(val, rb_intern(b"nsec\0".as_ptr() as *const c_char), 0);
446+
let sec = rb_funcall(val, recorder.to_i_id, 0);
447+
let nsec = rb_funcall(val, recorder.nsec_id, 0);
401448
return struct_value(
402449
recorder,
403450
"Time",
@@ -408,8 +455,8 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
408455
);
409456
}
410457
if rb_obj_is_kind_of(val, rb_cRegexp) != 0 {
411-
let src = rb_funcall(val, rb_intern(b"source\0".as_ptr() as *const c_char), 0);
412-
let opts = rb_funcall(val, rb_intern(b"options\0".as_ptr() as *const c_char), 0);
458+
let src = rb_funcall(val, recorder.source_id, 0);
459+
let opts = rb_funcall(val, recorder.options_id, 0);
413460
return struct_value(
414461
recorder,
415462
"Regexp",
@@ -422,8 +469,8 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
422469
if rb_obj_is_kind_of(val, rb_cStruct) != 0 {
423470
let class_name =
424471
cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Struct".to_string());
425-
let members = rb_funcall(val, rb_intern(b"members\0".as_ptr() as *const c_char), 0);
426-
let values = rb_funcall(val, rb_intern(b"values\0".as_ptr() as *const c_char), 0);
472+
let members = rb_funcall(val, recorder.members_id, 0);
473+
let values = rb_funcall(val, recorder.values_id, 0);
427474
if !RB_TYPE_P(members, rb_sys::ruby_value_type::RUBY_T_ARRAY)
428475
|| !RB_TYPE_P(values, rb_sys::ruby_value_type::RUBY_T_ARRAY)
429476
{
@@ -446,21 +493,18 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
446493
}
447494
return struct_value(recorder, &class_name, &names, &vals, depth, to_s_id);
448495
}
449-
let open_struct_id = rb_intern(b"OpenStruct\0".as_ptr() as *const c_char);
450-
if rb_const_defined(rb_cObject, open_struct_id) != 0 {
451-
let open_struct = rb_const_get(rb_cObject, open_struct_id);
452-
if rb_obj_is_kind_of(val, open_struct) != 0 {
453-
let h = rb_funcall(val, rb_intern(b"to_h\0".as_ptr() as *const c_char), 0);
454-
return to_value(recorder, h, depth - 1, to_s_id);
496+
if NIL_P(recorder.open_struct_class) {
497+
if rb_const_defined(rb_cObject, recorder.open_struct_const_id) != 0 {
498+
recorder.open_struct_class = rb_const_get(rb_cObject, recorder.open_struct_const_id);
455499
}
456500
}
501+
if !NIL_P(recorder.open_struct_class) && rb_obj_is_kind_of(val, recorder.open_struct_class) != 0 {
502+
let h = rb_funcall(val, recorder.to_h_id, 0);
503+
return to_value(recorder, h, depth - 1, to_s_id);
504+
}
457505
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Object".to_string());
458506
// generic object
459-
let ivars = rb_funcall(
460-
val,
461-
rb_intern(b"instance_variables\0".as_ptr() as *const c_char),
462-
0,
463-
);
507+
let ivars = rb_funcall(val, recorder.instance_variables_id, 0);
464508
if !RB_TYPE_P(ivars, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
465509
let text = value_to_string(val, to_s_id).unwrap_or_default();
466510
let type_id = recorder.tracer.ensure_type_id(TypeKind::Raw, &class_name);
@@ -476,12 +520,7 @@ unsafe fn to_value(recorder: &mut Recorder, val: VALUE, depth: usize, to_s_id: I
476520
let cstr = rb_id2name(id);
477521
let name = CStr::from_ptr(cstr).to_str().unwrap_or("?");
478522
names.push(name);
479-
let value = rb_funcall(
480-
val,
481-
rb_intern(b"instance_variable_get\0".as_ptr() as *const c_char),
482-
1,
483-
sym,
484-
);
523+
let value = rb_funcall(val, recorder.instance_variable_get_id, 1, sym);
485524
vals.push(value);
486525
}
487526
if !names.is_empty() {

0 commit comments

Comments
 (0)