ruby-changes:63776
From: Takashi <ko1@a...>
Date: Sat, 28 Nov 2020 14:39:28 +0900 (JST)
Subject: [ruby-changes:63776] 16dab6b692 (master): Run unload_units in the JIT worker thread
https://git.ruby-lang.org/ruby.git/commit/?id=16dab6b692 From 16dab6b69263ed9c816bc0283c8c1f2f95dc1027 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun <takashikkbn@g...> Date: Fri, 27 Nov 2020 21:28:57 -0800 Subject: Run unload_units in the JIT worker thread to avoid "Too many JIT code, but skipped unloading units for JIT compaction". Now we can forget the `in_compact` locking. Moving some functions from mjit.c to mjit_worker.c because mjit_worker.c should have functions executed in the JIT worker. diff --git a/mjit.c b/mjit.c index 170f223..94857df 100644 --- a/mjit.c +++ b/mjit.c @@ -166,16 +166,6 @@ free_list(struct rb_mjit_unit_list *list, bool close_handle_p) https://github.com/ruby/ruby/blob/trunk/mjit.c#L166 list->length = 0; } -// MJIT info related to an existing continutaion. -struct mjit_cont { - rb_execution_context_t *ec; // continuation ec - struct mjit_cont *prev, *next; // used to form lists -}; - -// Double linked list of registered continuations. This is used to detect -// units which are in use in unload_units. -static struct mjit_cont *first_cont; - // Register a new continuation with execution context `ec`. Return MJIT info about // the continuation. struct mjit_cont * @@ -253,87 +243,6 @@ create_unit(const rb_iseq_t *iseq) https://github.com/ruby/ruby/blob/trunk/mjit.c#L243 iseq->body->jit_unit = unit; } -// Set up field `used_code_p` for unit iseqs whose iseq on the stack of ec. -static void -mark_ec_units(rb_execution_context_t *ec) -{ - const rb_control_frame_t *cfp; - - if (ec->vm_stack == NULL) - return; - for (cfp = RUBY_VM_END_CONTROL_FRAME(ec) - 1; ; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { - const rb_iseq_t *iseq; - if (cfp->pc && (iseq = cfp->iseq) != NULL - && imemo_type((VALUE) iseq) == imemo_iseq - && (iseq->body->jit_unit) != NULL) { - iseq->body->jit_unit->used_code_p = true; - } - - if (cfp == ec->cfp) - break; // reached the most recent cfp - } -} - -// Unload JIT code of some units to satisfy the maximum permitted -// number of units with a loaded code. -static void -unload_units(void) -{ - struct rb_mjit_unit *unit = 0, *next, *worst; - struct mjit_cont *cont; - int delete_num, units_num = active_units.length; - - // For now, we don't unload units when ISeq is GCed. We should - // unload such ISeqs first here. - list_for_each_safe(&active_units.head, unit, next, unode) { - if (unit->iseq == NULL) { // ISeq is GCed. - remove_from_list(unit, &active_units); - free_unit(unit); - } - } - - // Detect units which are in use and can't be unloaded. - list_for_each(&active_units.head, unit, unode) { - assert(unit->iseq != NULL && unit->handle != NULL); - unit->used_code_p = false; - } - // All threads have a root_fiber which has a mjit_cont. Other normal fibers also - // have a mjit_cont. Thus we can check ISeqs in use by scanning ec of mjit_conts. - for (cont = first_cont; cont != NULL; cont = cont->next) { - mark_ec_units(cont->ec); - } - // TODO: check stale_units and unload unused ones! (note that the unit is not associated to ISeq anymore) - - // Remove 1/10 units more to decrease unloading calls. - // TODO: Calculate max total_calls in unit_queue and don't unload units - // whose total_calls are larger than the max. - delete_num = active_units.length / 10; - for (; active_units.length > mjit_opts.max_cache_size - delete_num;) { - // Find one unit that has the minimum total_calls. - worst = NULL; - list_for_each(&active_units.head, unit, unode) { - if (unit->used_code_p) // We can't unload code on stack. - continue; - - if (worst == NULL || worst->iseq->body->total_calls > unit->iseq->body->total_calls) { - worst = unit; - } - } - if (worst == NULL) - break; - - // Unload the worst node. - verbose(2, "Unloading unit %d (calls=%lu)", worst->id, worst->iseq->body->total_calls); - assert(worst->handle != NULL); - remove_from_list(worst, &active_units); - free_unit(worst); - } - - if (units_num > active_units.length) { - verbose(1, "Too many JIT code -- %d units unloaded", units_num - active_units.length); - } -} - static void mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_info *compile_info) { @@ -352,16 +261,7 @@ mjit_add_iseq_to_process(const rb_iseq_t *iseq, const struct rb_mjit_compile_inf https://github.com/ruby/ruby/blob/trunk/mjit.c#L261 CRITICAL_SECTION_START(3, "in add_iseq_to_process"); add_to_list(iseq->body->jit_unit, &unit_queue); if (active_units.length >= mjit_opts.max_cache_size) { - if (in_compact) { - verbose(1, "Too many JIT code, but skipped unloading units for JIT compaction"); - } else { - RB_DEBUG_COUNTER_INC(mjit_unload_units); - unload_units(); - } - if (active_units.length == mjit_opts.max_cache_size && mjit_opts.wait) { // Sometimes all methods may be in use - mjit_opts.max_cache_size++; // avoid infinite loop on `rb_mjit_wait_call`. Note that --jit-wait is just for testing. - verbose(1, "No units can be unloaded -- incremented max-cache-size to %d for --jit-wait", mjit_opts.max_cache_size); - } + unload_units_p = true; } verbose(3, "Sending wakeup signal to workers in mjit_add_iseq_to_process"); rb_native_cond_broadcast(&mjit_worker_wakeup); diff --git a/mjit_worker.c b/mjit_worker.c index a1b565d..ff03a1a 100644 --- a/mjit_worker.c +++ b/mjit_worker.c @@ -225,8 +225,8 @@ static rb_nativethread_cond_t mjit_gc_wakeup; https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L225 static int in_gc = 0; // True when JIT is working. static bool in_jit = false; -// True when JIT compaction is running. -static bool in_compact = false; +// True when unload_units is requested from Ruby threads. +static bool unload_units_p = false; // Set to true to stop worker. static bool stop_worker_p; // Set to true if worker is stopped. @@ -973,10 +973,6 @@ compact_all_jit_code(void) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L973 sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext); sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext); - CRITICAL_SECTION_START(3, "in compact_all_jit_code to guard .c files from unload_units"); - in_compact = true; - CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to guard .c files from unload_units"); - bool success = compile_compact_jit_code(c_file); double start_time = real_ms_time(); if (success) { @@ -986,10 +982,6 @@ compact_all_jit_code(void) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L982 } double end_time = real_ms_time(); - CRITICAL_SECTION_START(3, "in compact_all_jit_code to release .c files"); - in_compact = false; - CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to release .c files"); - if (success) { void *handle = dlopen(so_file, RTLD_NOW); if (handle == NULL) { @@ -1228,6 +1220,97 @@ mjit_capture_cc_entries(const struct rb_iseq_constant_body *compiled_iseq, const https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L1220 return cc_entries_index; } +// Set up field `used_code_p` for unit iseqs whose iseq on the stack of ec. +static void +mark_ec_units(rb_execution_context_t *ec) +{ + const rb_control_frame_t *cfp; + + if (ec->vm_stack == NULL) + return; + for (cfp = RUBY_VM_END_CONTROL_FRAME(ec) - 1; ; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { + const rb_iseq_t *iseq; + if (cfp->pc && (iseq = cfp->iseq) != NULL + && imemo_type((VALUE) iseq) == imemo_iseq + && (iseq->body->jit_unit) != NULL) { + iseq->body->jit_unit->used_code_p = true; + } + + if (cfp == ec->cfp) + break; // reached the most recent cfp + } +} + +// MJIT info related to an existing continutaion. +struct mjit_cont { + rb_execution_context_t *ec; // continuation ec + struct mjit_cont *prev, *next; // used to form lists +}; + +// Double linked list of registered continuations. This is used to detect +// units which are in use in unload_units. +static struct mjit_cont *first_cont; + +// Unload JIT code of some units to satisfy the maximum permitted +// number of units with a loaded code. +static void +unload_units(void) +{ + struct rb_mjit_unit *unit = 0, *next, *worst; + struct mjit_cont *cont; + int delete_num, units_num = active_units.length; + + // For now, we don't unload units when ISeq is GCed. We should + // unload such ISeqs first here. + list_for_each_safe(&active_units.head, unit, next, unode) { + if (unit->iseq == NULL) { // ISeq is GCed. + remove_from_list(unit, &active_units); + free_unit(unit); + } + } + + // Detect units which are in use and can't be unloaded. + list_for_each(&active_units.head, unit, unode) { + assert(unit->iseq != NULL && unit->handle != NULL); + unit->used_code_p = false; + } + // All threads have a root_fiber which has a mjit_cont. Other normal fibers also + // have a mjit_cont. Thus we can check ISeqs in use by scanning ec of mjit_conts. + for (cont = first_cont; cont != NULL; cont = cont->next) { + mark_ec_units(cont->ec); + } + // TODO: check stale_units and unload unused ones! (note that the unit is not associated to ISeq anymore) + + // Remove 1/10 units more to decrease unloading calls. + // TODO: Calculate max total_calls in unit_queue and don't unload units + // whose total_calls are larger than the max. + delete_num = active_units.length / 10; + for (; active_units.length > mjit_opts.max_cache_size - delete_num;) { + // Fi (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/