diff options
author | Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> | 2023-10-03 17:45:46 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-03 17:45:46 -0400 |
commit | ea491802fa4a6ce7070b318ffcad30cbeaf42635 (patch) | |
tree | e9a921cc4ef2773701e714d214f4e1f36ff9b246 /yjit | |
parent | d47af931105583c1504965300492422e5af86b81 (diff) | |
download | ruby-ea491802fa4a6ce7070b318ffcad30cbeaf42635.tar.gz |
YJIT: add heuristic to avoid compiling cold ISEQs (#8522)
* YJIT: Add counter to measure how often we compile "cold" ISEQs (#535)
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
Debug deadlock issue with test_ractor
Fix call threshold issue with tests
* Revert exception threshold logic. Document option in yjid.md
* (void) for 0 parameter functions in C99
* Rename iseq_entry_cold => cold_iseq_entry
* Document --yjit-cold-threshold in ruby.c
* Update doc/yjit/yjit.md
Co-authored-by: Jean byroot Boussier <jean.boussier+github@shopify.com>
* Shorten help string to appease test
* Address bug found by Kokubun. Reorder logic.
---------
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
Co-authored-by: Jean byroot Boussier <jean.boussier+github@shopify.com>
Diffstat (limited to 'yjit')
-rw-r--r-- | yjit/src/core.rs | 3 | ||||
-rw-r--r-- | yjit/src/options.rs | 12 | ||||
-rw-r--r-- | yjit/src/stats.rs | 4 | ||||
-rw-r--r-- | yjit/src/yjit.rs | 48 |
4 files changed, 64 insertions, 3 deletions
diff --git a/yjit/src/core.rs b/yjit/src/core.rs index aa1b12483d..fea48c1b87 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -986,6 +986,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>, + + // Used to estimate how frequently this ISEQ gets called + pub call_count_at_interv: u64, } impl IseqPayload { diff --git a/yjit/src/options.rs b/yjit/src/options.rs index 44254d1557..b09c827cfd 100644 --- a/yjit/src/options.rs +++ b/yjit/src/options.rs @@ -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, @@ -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, @@ -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(_) => { diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 7eae0cba21..24861205d1 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -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::cold_iseq_entry, Counter::compiled_iseq_count, Counter::compiled_blockid_count, Counter::compiled_block_count, @@ -441,6 +442,7 @@ make_counters! { binding_set, compiled_iseq_entry, + cold_iseq_entry, compiled_iseq_count, compiled_blockid_count, compiled_block_count, diff --git a/yjit/src/yjit.rs b/yjit/src/yjit.rs index 2aed3c6a4b..8d44e5ef6e 100644 --- a/yjit/src/yjit.rs +++ b/yjit/src/yjit.rs @@ -46,11 +46,55 @@ pub fn yjit_enabled_p() -> bool { YJIT_ENABLED.load(Ordering::Acquire) } +/// Make the call threshold available to C +#[no_mangle] +pub extern "C" fn rb_yjit_call_threshold() -> raw::c_ulong { + get_option!(call_threshold) as raw::c_ulong +} + +// Counter to serve as a proxy for execution time, total number of calls +static mut TOTAL_ENTRY_HITS: u64 = 0; + +// Number of calls used to estimate how hot an ISEQ is +static CALL_COUNT_INTERV: u64 = 20; + /// 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; } + + // Record the number of calls at the beginning of the interval + if total_calls + CALL_COUNT_INTERV == call_threshold { + let payload = get_or_create_iseq_payload(iseq); + let call_count = unsafe { TOTAL_ENTRY_HITS }; + payload.call_count_at_interv = 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 { + // We expect threshold 1 to compile everything immediately + if call_threshold < CALL_COUNT_INTERV { + return true; + } + + let payload = get_or_create_iseq_payload(iseq); + let call_count = unsafe { TOTAL_ENTRY_HITS }; + let num_calls = call_count - payload.call_count_at_interv; + + // Reject ISEQs that don't get called often enough + if num_calls > get_option!(cold_threshold) as u64 { + incr_counter!(cold_iseq_entry); + return false; + } + + return true; + } + + return false; } /// This function is called from C code |