aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean Boussier <byroot@ruby-lang.org>2022-10-19 16:56:37 +0200
committerJean Boussier <jean.boussier@gmail.com>2023-04-04 19:49:08 +0200
commitba6ccd871442f55080bffd53e33678c0726787d2 (patch)
treed8b9435500b80ecdd29e798db76cb4620b5763b7
parenta84c99468f26a9f79fec57926d561ed906505eac (diff)
downloadruby-ba6ccd871442f55080bffd53e33678c0726787d2.tar.gz
Implement `Process.warmup`
[Feature #18885] For now, the optimizations performed are: - Run a major GC - Compact the heap - Promote all surviving objects to oldgen Other optimizations may follow.
-rw-r--r--NEWS.md7
-rw-r--r--common.mk2
-rw-r--r--gc.c31
-rw-r--r--internal/gc.h1
-rw-r--r--process.c34
-rw-r--r--spec/ruby/core/process/warmup_spec.rb11
-rw-r--r--test/ruby/test_process.rb8
7 files changed, 94 insertions, 0 deletions
diff --git a/NEWS.md b/NEWS.md
index afcfb374fc..1b9f87c09b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -35,6 +35,13 @@ Note: We're only listing outstanding class updates.
The class use equality semantic to lookup keys like a regular hash,
but it doesn't hold strong references on the keys. [[Feature #18498]]
+* Process.warnup
+
+ * 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.
diff --git a/common.mk b/common.mk
index b1cf9b05a2..e186b06d73 100644
--- a/common.mk
+++ b/common.mk
@@ -11024,7 +11024,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
diff --git a/gc.c b/gc.c
index 3dc63433a7..e52e3fc35b 100644
--- a/gc.c
+++ b/gc.c
@@ -9833,6 +9833,26 @@ garbage_collect_with_gvl(rb_objspace_t *objspace, unsigned int reason)
}
}
+static int
+gc_promote_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_OLD(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)
{
@@ -9860,6 +9880,17 @@ gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE
return Qnil;
}
+void
+rb_gc_prepare_heap(void)
+{
+ gc_start_internal(NULL, Qtrue, Qtrue, Qtrue, Qtrue, Qtrue);
+
+ /* The transient heap need to be evacuated before we promote objects */
+ rb_transient_heap_start_marking(true);
+ rb_transient_heap_evacuate();
+ rb_objspace_each_objects(gc_promote_object_i, NULL);
+}
+
static int
gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj)
{
diff --git a/internal/gc.h b/internal/gc.h
index 2b67ca40dc..e0cc3bfa8a 100644
--- a/internal/gc.h
+++ b/internal/gc.h
@@ -214,6 +214,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 *);
diff --git a/process.c b/process.c
index daf3456bfd..9605036c64 100644
--- a/process.c
+++ b/process.c
@@ -115,6 +115,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 */
@@ -8510,6 +8511,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
@@ -8627,6 +8659,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..fbdfd34848
--- /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 dependant.
+ # 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 6e8050a54a..fa25d10353 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
@@ -2679,4 +2680,11 @@ 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
end