Skip to content

Commit

Permalink
Add REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
Browse files Browse the repository at this point in the history
[Feature #19571]

This commit adds the environment variable
`RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO` which is
used to calculate the `remembered_wb_unprotected_objects_limit` using a
ratio of `old_objects`. This should improve performance by reducing
major GC because, in a major GC, we mark all of the old objects, so we
should have more uncollectible WB unprotected objects before starting a
major GC. The default has been set to 0.01 (1% of old objects).

On one of [Shopify's highest traffic Ruby apps, Storefront Renderer](https://shopify.engineering/how-shopify-reduced-storefront-response-times-rewrite),
we saw significant improvements after deploying this patch in
production. In the graphs below, we have the `tuned` group which uses
`RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO=0.01` (the
default value), and an `untuned` group, which turns this feature off
with `RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO=0`. We
see that the tuned group spends significantly less time in GC, on
average 0.67x of the time compared to the untuned group and 0.49x for
p99. We see this improvement in GC time translate to improvements in
response times. The average response time is now 0.96x of the time
compared to the untuned group and 0.86x for p99.

https://user-images.githubusercontent.com/15860699/229559078-e23e8ce4-5f1f-4a2f-b5ef-5769f92b8c70.png
  • Loading branch information
peterzhu2118 committed May 24, 2023
1 parent 6d976eb commit a23ae56
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 1 deletion.
11 changes: 10 additions & 1 deletion gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ rb_gc_guarded_ptr_val(volatile VALUE *ptr, VALUE val)
#ifndef GC_HEAP_GROWTH_MAX_SLOTS
#define GC_HEAP_GROWTH_MAX_SLOTS 0 /* 0 is disable */
#endif
#ifndef GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
# define GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO 0.01
#endif
#ifndef GC_HEAP_OLDOBJECT_LIMIT_FACTOR
#define GC_HEAP_OLDOBJECT_LIMIT_FACTOR 2.0
#endif
Expand Down Expand Up @@ -347,6 +350,7 @@ typedef struct {
double heap_free_slots_min_ratio;
double heap_free_slots_goal_ratio;
double heap_free_slots_max_ratio;
double uncollectible_wb_unprotected_objects_limit_ratio;
double oldobject_limit_factor;

size_t malloc_limit_min;
Expand All @@ -369,6 +373,7 @@ static ruby_gc_params_t gc_params = {
GC_HEAP_FREE_SLOTS_MIN_RATIO,
GC_HEAP_FREE_SLOTS_GOAL_RATIO,
GC_HEAP_FREE_SLOTS_MAX_RATIO,
GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO,
GC_HEAP_OLDOBJECT_LIMIT_FACTOR,

GC_MALLOC_LIMIT_MIN,
Expand Down Expand Up @@ -8346,7 +8351,10 @@ gc_marks_finish(rb_objspace_t *objspace)
if (full_marking) {
/* See the comment about RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR */
const double r = gc_params.oldobject_limit_factor;
objspace->rgengc.uncollectible_wb_unprotected_objects_limit = (size_t)(objspace->rgengc.uncollectible_wb_unprotected_objects * r);
objspace->rgengc.uncollectible_wb_unprotected_objects_limit = MAX(
(size_t)(objspace->rgengc.uncollectible_wb_unprotected_objects * r),
(size_t)(objspace->rgengc.old_objects * gc_params.uncollectible_wb_unprotected_objects_limit_ratio)
);
objspace->rgengc.old_objects_limit = (size_t)(objspace->rgengc.old_objects * r);
}

Expand Down Expand Up @@ -11749,6 +11757,7 @@ ruby_gc_set_params(void)
get_envparam_double("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", &gc_params.heap_free_slots_goal_ratio,
gc_params.heap_free_slots_min_ratio, gc_params.heap_free_slots_max_ratio, TRUE);
get_envparam_double("RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR", &gc_params.oldobject_limit_factor, 0.0, 0.0, TRUE);
get_envparam_double("RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO", &gc_params.uncollectible_wb_unprotected_objects_limit_ratio, 0.0, 0.0, TRUE);

if (get_envparam_size("RUBY_GC_MALLOC_LIMIT", &gc_params.malloc_limit_min, 0)) {
malloc_limit = gc_params.malloc_limit_min;
Expand Down
9 changes: 9 additions & 0 deletions test/ruby/test_gc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,15 @@ def test_gc_parameter
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "")
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
end

["0.01", "0.1", "1.0"].each do |i|
env = {"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0", "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO" => i}
assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY)
GC.disable
GC.start
assert_equal((GC.stat[:old_objects] * #{i}).to_i, GC.stat[:remembered_wb_unprotected_objects_limit])
RUBY
end
end

def test_profiler_enabled
Expand Down

0 comments on commit a23ae56

Please sign in to comment.