Project

General

Profile

Feature #15878

Make exit faster by not running GC

Added by grosser (Michael Grosser) about 2 months ago. Updated about 1 month ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:92851]

Description

I noticed that exit takes 0.2 ... I'm trying to write a fast cli, so any improvement here would be great or an option to opt-out of certain cleanup tasks
exit! takes a constant low time

ruby -rbenchmark -e 'puts Benchmark.realtime { Process.wait(fork { exit }) }' # 0.03 great!
ruby -rbenchmark -rrubocop -e 'puts Benchmark.realtime { Process.wait(fork { exit }) }' # 0.18 :(
ruby -rbenchmark -rrubocop -e 'GC.disable; puts Benchmark.realtime { Process.wait(fork { exit }) }' # 0.04 :D
ruby -rbenchmark -rrubocop -e 'puts Benchmark.realtime { Process.wait(fork { exit! }) }' # 0.002 ... fast but unsafe

History

Updated by shyouhei (Shyouhei Urabe) about 2 months ago

IMHO it is challenging to make a GC-less exit fundamentally safer than exit!
GC does some important tidy-up tasks for you, like ensuring contents in IO buffers flushed.

Updated by chrisseaton (Chris Seaton) about 2 months ago

Is Ruby's GC conservative? So even if a GC is performed at exit are IO buffers guaranteed to be flushed?

Updated by shyouhei (Shyouhei Urabe) about 2 months ago

chrisseaton (Chris Seaton) wrote:

Is Ruby's GC conservative? So even if a GC is performed at exit are IO buffers guaranteed to be flushed?

Yes and yes. There is a special handling code for process termination, somewhere around https://github.com/ruby/ruby/blob/trunk/eval.c#L163-L172
A rough sketch of the process is that we first run GC normally, then if something remains, that is force-recycled.

Updated by ko1 (Koichi Sasada) about 1 month ago

A rough sketch of the process is that we first run GC normally, then if something remains, that is force-recycled.

No. We don't run GC at last.

diff --git a/gc.c b/gc.c
index 19ddf9bf20..d9f83dfaf8 100644
--- a/gc.c
+++ b/gc.c
@@ -7183,6 +7183,7 @@ gc_enter(rb_objspace_t *objspace, const char *event)
     mjit_gc_start_hook();

     during_gc = TRUE;
+    fprintf(stderr, "gc_enter: %s [%s]\n", event, gc_current_status(objspace));
     gc_report(1, objspace, "gc_enter: %s [%s]\n", event, gc_current_status(objspace));
     gc_record(objspace, 0, event);
     gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_ENTER, 0); /* TODO: which parameter should be passed? */

This patch shows GC events every time. Output:

$ ./miniruby -e 'a=[];10000.times{a<<[]}; p "to be exit"'
gc_enter: gc_start [N]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: sweep_continue [SL]
gc_enter: gc_start [N]
gc_enter: sweep_continue [SL]
"to be exit"
gc_enter: rb_objspace_call_finalizer [N]

rb_objspace_call_finalizer run finalizers for all objects (don't collect, but run special finalize functions).

Also available in: Atom PDF