diff options
-rw-r--r-- | NEWS.md | 7 | ||||
-rw-r--r-- | common.mk | 2 | ||||
-rw-r--r-- | gc.c | 27 | ||||
-rw-r--r-- | internal/gc.h | 1 | ||||
-rw-r--r-- | process.c | 34 | ||||
-rw-r--r-- | spec/ruby/core/process/warmup_spec.rb | 11 | ||||
-rw-r--r-- | test/ruby/test_process.rb | 17 |
7 files changed, 99 insertions, 0 deletions
@@ -49,6 +49,13 @@ Note: We're only listing outstanding class updates. * `Module#set_temporary_name` added for setting a temporary name for a module. [[Feature #19521]] +* Process.warmup + + * Notify the Ruby virtual machine that the boot sequence is finished, + and that now is a good time to optimize the application. This is useful + for long running applications. The actual optimizations performed are entirely + implementation specific and may change in the future without notice. [[Feature #18885] + ## Stdlib updates The following default gems are updated. @@ -11649,7 +11649,9 @@ process.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h process.$(OBJEXT): {$(VPATH)}thread_native.h process.$(OBJEXT): {$(VPATH)}util.h process.$(OBJEXT): {$(VPATH)}vm_core.h +process.$(OBJEXT): {$(VPATH)}vm_debug.h process.$(OBJEXT): {$(VPATH)}vm_opts.h +process.$(OBJEXT): {$(VPATH)}vm_sync.h ractor.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h ractor.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h ractor.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -9590,6 +9590,26 @@ garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason) } } +static int +gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data) +{ + rb_objspace_t *objspace = &rb_objspace; + VALUE v = (VALUE)vstart; + for (; v != (VALUE)vend; v += stride) { + switch (BUILTIN_TYPE(v)) { + case T_NONE: + case T_ZOMBIE: + break; + default: + if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) { + RVALUE_AGE_SET_CANDIDATE(objspace, v); + } + } + } + + return 0; +} + static VALUE gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE immediate_mark, VALUE immediate_sweep, VALUE compact) { @@ -9617,6 +9637,13 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE return Qnil; } +void +rb_gc_prepare_heap(void) +{ + rb_objspace_each_objects(gc_set_candidate_object_i, NULL); + gc_start_internal(NULL, Qtrue, Qtrue, Qtrue, Qtrue, Qtrue); +} + static int gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj) { diff --git a/internal/gc.h b/internal/gc.h index e345f20cb6..2934422474 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -204,6 +204,7 @@ extern VALUE *ruby_initial_gc_stress_ptr; extern int ruby_disable_gc; RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size); void ruby_mimfree(void *ptr); +void rb_gc_prepare_heap(void); void rb_objspace_set_event_hook(const rb_event_flag_t event); VALUE rb_objspace_gc_enable(struct rb_objspace *); VALUE rb_objspace_gc_disable(struct rb_objspace *); @@ -116,6 +116,7 @@ int initgroups(const char *, rb_gid_t); #include "ruby/thread.h" #include "ruby/util.h" #include "vm_core.h" +#include "vm_sync.h" #include "ruby/ractor.h" /* define system APIs */ @@ -8534,6 +8535,37 @@ static VALUE rb_mProcUID; static VALUE rb_mProcGID; static VALUE rb_mProcID_Syscall; +/* + * call-seq: + * Process.warmup -> true + * + * Notify the Ruby virtual machine that the boot sequence is finished, + * and that now is a good time to optimize the application. This is useful + * for long running applications. + * + * This method is expected to be called at the end of the application boot. + * If the application is deployed using a pre-forking model, +Process.warmup+ + * should be called in the original process before the first fork. + * + * The actual optimizations performed are entirely implementation specific + * and may change in the future without notice. + * + * On CRuby, +Process.warmup+: + * + * * Perform a major GC. + * * Compacts the heap. + * * Promotes all surviving objects to the old generation. + */ + +static VALUE +proc_warmup(VALUE _) +{ + RB_VM_LOCK_ENTER(); + rb_gc_prepare_heap(); + RB_VM_LOCK_LEAVE(); + return Qtrue; +} + /* * Document-module: Process @@ -8651,6 +8683,8 @@ InitVM_process(void) rb_define_module_function(rb_mProcess, "getpriority", proc_getpriority, 2); rb_define_module_function(rb_mProcess, "setpriority", proc_setpriority, 3); + rb_define_module_function(rb_mProcess, "warmup", proc_warmup, 0); + #ifdef HAVE_GETPRIORITY /* see Process.setpriority */ rb_define_const(rb_mProcess, "PRIO_PROCESS", INT2FIX(PRIO_PROCESS)); diff --git a/spec/ruby/core/process/warmup_spec.rb b/spec/ruby/core/process/warmup_spec.rb new file mode 100644 index 0000000000..b562d52d22 --- /dev/null +++ b/spec/ruby/core/process/warmup_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' + +describe "Process.warmup" do + ruby_version_is "3.3" do + # The behavior is entirely implementation specific. + # Other implementations are free to just make it a noop + it "is implemented" do + Process.warmup.should == true + end + end +end diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 635ffef4ee..3642f955e8 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -4,6 +4,7 @@ require 'test/unit' require 'tempfile' require 'timeout' require 'rbconfig' +require 'objspace' class TestProcess < Test::Unit::TestCase RUBY = EnvUtil.rubybin @@ -2686,4 +2687,20 @@ EOS end end; end if Process.respond_to?(:_fork) + + def test_warmup_promote_all_objects_to_oldgen + obj = Object.new + + refute_includes(ObjectSpace.dump(obj), '"old":true') + Process.warmup + assert_includes(ObjectSpace.dump(obj), '"old":true') + end + + def test_warmup_run_major_gc_and_compact + major_gc_count = GC.stat(:major_gc_count) + compact_count = GC.stat(:compact_count) + Process.warmup + assert_equal major_gc_count + 1, GC.stat(:major_gc_count) + assert_equal compact_count + 1, GC.stat(:compact_count) + end end |