mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
ZJIT: Use Vec instead of HashMap for profiling (#13809)
This is notably faster: no need to hash indices.
Before:
```
plum% samply record ~/.rubies/ruby-zjit/bin/ruby --zjit benchmarks/getivar.rb
ruby 3.5.0dev (2025-07-10T14:40:49Z master 51252ef8d7
) +ZJIT dev +PRISM [arm64-darwin24]
itr: time
#1: 5311ms
#2: 49ms
#3: 49ms
#4: 48ms
```
After:
```
plum% samply record ~/.rubies/ruby-zjit/bin/ruby --zjit benchmarks/getivar.rb
ruby 3.5.0dev (2025-07-10T15:09:06Z mb-benchmark-compile 42ffd3c1ee) +ZJIT dev +PRISM [arm64-darwin24]
itr: time
#1: 1332ms
#2: 49ms
#3: 48ms
#4: 48ms
```
This commit is contained in:
parent
b760afe2b7
commit
b0b1712b52
2 changed files with 21 additions and 16 deletions
|
@ -4,7 +4,7 @@ use std::ffi::c_void;
|
|||
use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr};
|
||||
|
||||
/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct IseqPayload {
|
||||
/// Type information of YARV instruction operands
|
||||
pub profile: IseqProfile,
|
||||
|
@ -15,6 +15,12 @@ pub struct IseqPayload {
|
|||
// TODO: Add references to GC offsets in JIT code
|
||||
}
|
||||
|
||||
impl IseqPayload {
|
||||
fn new(iseq_size: u32) -> Self {
|
||||
Self { profile: IseqProfile::new(iseq_size), start_ptr: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the payload object associated with an iseq. Create one if none exists.
|
||||
pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
|
||||
type VoidPtr = *mut c_void;
|
||||
|
@ -26,7 +32,8 @@ pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
|
|||
// We drop the payload with Box::from_raw when the GC frees the iseq and calls us.
|
||||
// NOTE(alan): Sometimes we read from an iseq without ever writing to it.
|
||||
// We allocate in those cases anyways.
|
||||
let new_payload = IseqPayload::default();
|
||||
let iseq_size = get_iseq_encoded_size(iseq);
|
||||
let new_payload = IseqPayload::new(iseq_size);
|
||||
let new_payload = Box::into_raw(Box::new(new_payload));
|
||||
rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr);
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// We use the YARV bytecode constants which have a CRuby-style name
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{cruby::*, gc::get_or_create_iseq_payload, hir_type::{types::{Empty, Fixnum}, Type}};
|
||||
|
||||
/// Ephemeral state for profiling runtime information
|
||||
|
@ -77,30 +75,30 @@ fn profile_insn(profiler: &mut Profiler, opcode: ruby_vminsn_type) {
|
|||
/// Profile the Type of top-`n` stack operands
|
||||
fn profile_operands(profiler: &mut Profiler, n: usize) {
|
||||
let profile = &mut get_or_create_iseq_payload(profiler.iseq).profile;
|
||||
let mut types = if let Some(types) = profile.opnd_types.get(&profiler.insn_idx) {
|
||||
types.clone()
|
||||
} else {
|
||||
vec![Empty; n]
|
||||
};
|
||||
|
||||
let types = &mut profile.opnd_types[profiler.insn_idx];
|
||||
if types.len() <= n {
|
||||
types.resize(n, Empty);
|
||||
}
|
||||
for i in 0..n {
|
||||
let opnd_type = Type::from_value(profiler.peek_at_stack((n - i - 1) as isize));
|
||||
types[i] = types[i].union(opnd_type);
|
||||
}
|
||||
|
||||
profile.opnd_types.insert(profiler.insn_idx, types);
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct IseqProfile {
|
||||
/// Type information of YARV instruction operands, indexed by the instruction index
|
||||
opnd_types: HashMap<usize, Vec<Type>>,
|
||||
opnd_types: Vec<Vec<Type>>,
|
||||
}
|
||||
|
||||
impl IseqProfile {
|
||||
pub fn new(iseq_size: u32) -> Self {
|
||||
Self { opnd_types: vec![vec![]; iseq_size as usize] }
|
||||
}
|
||||
|
||||
/// Get profiled operand types for a given instruction index
|
||||
pub fn get_operand_types(&self, insn_idx: usize) -> Option<&[Type]> {
|
||||
self.opnd_types.get(&insn_idx).map(|types| types.as_slice())
|
||||
self.opnd_types.get(insn_idx).map(|v| &**v)
|
||||
}
|
||||
|
||||
/// Return true if top-two stack operands are Fixnums
|
||||
|
@ -113,7 +111,7 @@ impl IseqProfile {
|
|||
|
||||
/// Run a given callback with every object in IseqProfile
|
||||
pub fn each_object(&self, callback: impl Fn(VALUE)) {
|
||||
for types in self.opnd_types.values() {
|
||||
for types in &self.opnd_types {
|
||||
for opnd_type in types {
|
||||
if let Some(object) = opnd_type.ruby_object() {
|
||||
callback(object);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue