Skip to content

Commit

Permalink
YJIT: Add counter to measure how often we compile "cold" ISEQs (#535)
Browse files Browse the repository at this point in the history
Fix counter name in DEFAULT_COUNTERS

YJIT: add --yjit-cold-threshold, don't compile cold ISEQs

YJIT: increase default cold threshold to 200_000

Remove rb_yjit_call_threshold()

Remove conflict markers

Fix compilation errors

Threshold 1 should compile immediately
  • Loading branch information
maximecb committed Sep 28, 2023
1 parent 809b67b commit a219295
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 3 deletions.
1 change: 1 addition & 0 deletions yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def _print_stats(out: $stderr) # :nodoc:
out.puts "bindings_set: " + format_number(13, stats[:binding_set])
out.puts "compilation_failure: " + format_number(13, compilation_failure) if compilation_failure != 0
out.puts "compiled_iseq_entry: " + format_number(13, stats[:compiled_iseq_entry])
out.puts "iseq_entry_cold: " + format_number_pct(13, stats[:iseq_entry_cold], stats[:compiled_iseq_entry])
out.puts "compiled_iseq_count: " + format_number(13, stats[:compiled_iseq_count])
out.puts "compiled_blockid_count:" + format_number(13, stats[:compiled_blockid_count])
out.puts "compiled_block_count: " + format_number(13, stats[:compiled_block_count])
Expand Down
3 changes: 3 additions & 0 deletions yjit/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,9 @@ pub struct IseqPayload {
// Blocks that are invalidated but are not yet deallocated.
// The code GC will free them later.
pub dead_blocks: Vec<BlockRef>,

// Number of calls when we hit the threshold for this ISEQs
pub call_count_at_threshold: u64,
}

impl IseqPayload {
Expand Down
12 changes: 12 additions & 0 deletions yjit/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub struct Options {
// Threshold==1 means compile on first execution
pub call_threshold: usize,

// Number of execution requests after which a method is no longer
// considered hot. Raising this results in more generated code.
pub cold_threshold: usize,

// Generate versions greedily until the limit is hit
pub greedy_versioning: bool,

Expand Down Expand Up @@ -59,6 +63,7 @@ pub struct Options {
pub static mut OPTIONS: Options = Options {
exec_mem_size: 128 * 1024 * 1024,
call_threshold: 30,
cold_threshold: 200_000,
greedy_versioning: false,
no_type_prop: false,
max_versions: 4,
Expand Down Expand Up @@ -143,6 +148,13 @@ pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
}
},

("cold-threshold", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.cold_threshold = n },
Err(_) => {
return None;
}
},

("max-versions", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.max_versions = n },
Err(_) => {
Expand Down
4 changes: 3 additions & 1 deletion yjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,10 @@ macro_rules! make_counters {

/// The list of counters that are available without --yjit-stats.
/// They are incremented only by `incr_counter!` and don't use `gen_counter_incr`.
pub const DEFAULT_COUNTERS: [Counter; 7] = [
pub const DEFAULT_COUNTERS: [Counter; 8] = [
Counter::code_gc_count,
Counter::compiled_iseq_entry,
Counter::iseq_entry_cold,
Counter::compiled_iseq_count,
Counter::compiled_blockid_count,
Counter::compiled_block_count,
Expand Down Expand Up @@ -441,6 +442,7 @@ make_counters! {
binding_set,

compiled_iseq_entry,
iseq_entry_cold,
compiled_iseq_count,
compiled_blockid_count,
compiled_block_count,
Expand Down
40 changes: 38 additions & 2 deletions yjit/src/yjit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,47 @@ pub fn yjit_enabled_p() -> bool {
YJIT_ENABLED.load(Ordering::Acquire)
}

// Counter to serve as a proxy for execution time, total number of calls
static mut TOTAL_ENTRY_HITS: u64 = 0;

/// Test whether we are ready to compile an ISEQ or not
#[no_mangle]
pub extern "C" fn rb_yjit_threshold_hit(_iseq: IseqPtr, total_calls: u64) -> bool {
pub extern "C" fn rb_yjit_threshold_hit(iseq: IseqPtr, total_calls: u64) -> bool {
let call_threshold = get_option!(call_threshold) as u64;
return total_calls == call_threshold;

unsafe { TOTAL_ENTRY_HITS += 1; }

if total_calls >= call_threshold {
// We expect threshold 1 to compile everything immediately
if call_threshold == 1 {
return true;
}

// If we are at the threshold
if total_calls == call_threshold {
let payload = get_or_create_iseq_payload(iseq);
let call_count = unsafe { TOTAL_ENTRY_HITS };
payload.call_count_at_threshold = call_count;
}

// Try to estimate the total time taken (total number of calls) to reach 20 calls to this ISEQ
// This give us a ratio of how hot/cold this ISEQ is
if total_calls == call_threshold + 20 {
let payload = get_or_create_iseq_payload(iseq);
let call_count = unsafe { TOTAL_ENTRY_HITS };
let num_calls = call_count - payload.call_count_at_threshold;

// Reject ISEQs that don't get called often enough
if num_calls > get_option!(cold_threshold) as u64 {
incr_counter!(iseq_entry_cold);
return false;
}

return true;
}
}

return false;
}

/// This function is called from C code
Expand Down

0 comments on commit a219295

Please sign in to comment.