summaryrefslogtreecommitdiff
path: root/yjit
diff options
context:
space:
mode:
authorMaxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>2023-10-03 17:45:46 -0400
committerGitHub <noreply@github.com>2023-10-03 17:45:46 -0400
commitea491802fa4a6ce7070b318ffcad30cbeaf42635 (patch)
treee9a921cc4ef2773701e714d214f4e1f36ff9b246 /yjit
parentd47af931105583c1504965300492422e5af86b81 (diff)
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.rs3
-rw-r--r--yjit/src/options.rs12
-rw-r--r--yjit/src/stats.rs4
-rw-r--r--yjit/src/yjit.rs48
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