summaryrefslogtreecommitdiff
path: root/gc
AgeCommit message (Collapse)Author
42 hours[ruby/mmtk] Implement object movement support for finalizer tablePeter Zhu
https://github.com/ruby/mmtk/commit/e5e2c1c347
3 days[ruby/mmtk] Clear slot when object is freedPeter Zhu
https://github.com/ruby/mmtk/commit/87290e45b2
7 days[ruby/mmtk] Assert that objects are not T_NONE in the write barrierPeter Zhu
https://github.com/ruby/mmtk/commit/59d27203e2
7 days[ruby/mmtk] Add MMTK_ASSERTPeter Zhu
https://github.com/ruby/mmtk/commit/e34d5cf32f
9 days[ruby/mmtk] Check that a and b are valid objects in write barrierPeter Zhu
https://github.com/ruby/mmtk/commit/350625ebb3
9 days[ruby/mmtk] Set MMTK_DEBUG in C compiler for debug buildsPeter Zhu
https://github.com/ruby/mmtk/commit/12a3904b04
10 days[ruby/mmtk] Assert pinning object is not movedPeter Zhu
https://github.com/ruby/mmtk/commit/8813e76bf8
12 days[ruby/mmtk] Process obj_free candidates in parallelPeter Zhu
Redos commit 544770d which seems to have accidentally been undone in b27d935.
12 daysAlso output GC thread backtrace in rb_mmtk_gc_thread_bugPeter Zhu
12 daysAdd rb_gc_print_backtracePeter Zhu
13 days[ruby/mmtk] Use rb_mmtk_gc_thread_bug for rb_mmtk_call_object_closurePeter Zhu
https://github.com/ruby/mmtk/commit/308936296a
13 days[ruby/mmtk] Propagate crash of GC thread to mutator threadPeter Zhu
This allows the mutator thread to dump its backtrace when a GC thread crashes. https://github.com/ruby/mmtk/commit/40ff9ffee7
13 days[ruby/mmtk] Check for T_NONE during markingPeter Zhu
https://github.com/ruby/mmtk/commit/c3e338bb25
13 days[ruby/mmtk] Assert target is not pinned during normal tracingPeter Zhu
https://github.com/ruby/mmtk/commit/58210c88ed
2026-01-01[ruby/mmtk] Format imports to be each on a new linePeter Zhu
https://github.com/ruby/mmtk/commit/42adba630e
2025-12-31Use `is_obj_encoding` instead of `is_data_encoding`Nobuyoshi Nakada
The argument to `is_data_encoding` is assumed to be `T_DATA`.
2025-12-31[ruby/mmtk] Split ProcessObjFreeCandidates to parallel and non-parallelPeter Zhu
This makes it easier to visualize in profilers which one is non-parallel. https://github.com/ruby/mmtk/commit/ba68b2ef3b
2025-12-31[ruby/mmtk] Process obj_free candidates in parallelPeter Zhu
This commit allows objects that are safe to be freed in parallel to do so. A decrease in object freeing time can be seen in profiles. The benchmarks don't show much difference. Before: -------------- -------------------- ---------- --------- bench sequential free (ms) stddev (%) RSS (MiB) activerecord 242.3 7.4 84.3 chunky-png 439.1 0.6 75.6 erubi-rails 1221.2 4.2 132.7 hexapdf 1544.8 1.8 429.1 liquid-c 42.7 7.4 48.5 liquid-compile 41.4 8.3 52.2 liquid-render 100.6 3.0 56.8 mail 108.9 2.1 65.1 psych-load 1536.9 0.6 43.4 railsbench 1633.5 2.6 146.2 rubocop 126.5 15.8 142.1 ruby-lsp 129.6 9.7 112.2 sequel 47.9 6.5 44.6 shipit 1152.0 2.7 315.2 -------------- -------------------- ---------- --------- After: -------------- ------------------ ---------- --------- bench parallel free (ms) stddev (%) RSS (MiB) activerecord 235.1 5.5 87.4 chunky-png 440.8 0.8 68.1 erubi-rails 1105.3 0.8 128.0 hexapdf 1578.3 4.1 405.1 liquid-c 42.6 7.1 48.4 liquid-compile 41.5 8.1 52.1 liquid-render 101.2 2.8 53.3 mail 109.7 2.7 64.8 psych-load 1567.7 1.1 44.4 railsbench 1644.9 1.9 150.9 rubocop 125.6 15.4 148.5 ruby-lsp 127.9 5.8 104.6 sequel 48.2 6.1 44.1 shipit 1215.3 4.7 320.5 -------------- ------------------ ---------- --------- https://github.com/ruby/mmtk/commit/4f0b5fd2eb
2025-12-30[ruby/mmtk] Use MMTK_HEAP_COUNT for SIZE_POOL_COUNTPeter Zhu
https://github.com/ruby/mmtk/commit/290a2aec4e
2025-12-30Add RVALUE_OLD_AGE to GC::INTERNAL_CONSTANTS for MMTkPeter Zhu
2025-12-30Fix generational GC for weak referencesPeter Zhu
Fixes issue pointed out in https://bugs.ruby-lang.org/issues/21084#note-7. The following script crashes: wmap = ObjectSpace::WeakMap.new GC.disable # only manual GCs GC.start GC.start retain = [] 50.times do k = Object.new wmap[k] = true retain << k end GC.start # wmap promoted, other objects still young retain.clear GC.start(full_mark: false) wmap.keys.each(&:itself) # call method on keys to cause crash
2025-12-29Implement moving Immix in MMTkPeter Zhu
This commit implements moving Immix in MMTk, which allows objects to move in the GC. The performance of this implementation is not yet amazing. It is very similar to non-moving Immix in many of them and slightly slower in others. The benchmark results is shown below. -------------- ----------------- ---------- --------- bench Moving Immix (ms) stddev (%) RSS (MiB) activerecord 241.9 0.5 86.6 chunky-png 447.8 0.8 74.9 erubi-rails 1183.9 0.8 136.1 hexapdf 1607.9 2.6 402.3 liquid-c 45.4 6.7 44.9 liquid-compile 44.1 9.3 53.0 liquid-render 105.4 4.5 55.9 lobsters 650.1 9.7 418.4 mail 115.4 2.1 64.4 psych-load 1656.8 0.8 43.6 railsbench 1653.5 1.3 149.8 rubocop 127.0 15.6 142.1 ruby-lsp 130.7 10.5 99.4 sequel 52.8 7.2 45.6 shipit 1187.0 3.9 311.0 -------------- ----------------- ---------- --------- -------------- --------------------- ---------- --------- bench Non-moving Immix (ms) stddev (%) RSS (MiB) activerecord 218.9 2.7 86.1 chunky-png 464.6 0.8 66.7 erubi-rails 1119.0 4.3 132.7 hexapdf 1539.8 1.8 425.2 liquid-c 40.6 6.9 45.2 liquid-compile 40.6 8.1 52.9 liquid-render 99.3 2.3 48.3 mail 107.4 5.3 65.4 psych-load 1535.6 1.0 39.5 railsbench 1565.6 1.1 149.6 rubocop 122.5 14.3 146.7 ruby-lsp 128.4 10.7 106.4 sequel 44.1 4.0 45.7 shipit 1154.5 2.7 358.5 -------------- --------------------- ---------- ---------
2025-12-29Add rb_gc_move_obj_during_markingPeter Zhu
2025-12-29Add rb_gc_register_pinning_objPeter Zhu
2025-12-26[ruby/mmtk] Fix cargo fmtPeter Zhu
https://github.com/ruby/mmtk/commit/f4c46cabc7
2025-12-25Implement declarative weak references in MMTkPeter Zhu
2025-12-25Implement declaring weak referencesPeter Zhu
[Feature #21084] # Summary The current way of marking weak references uses `rb_gc_mark_weak(VALUE *ptr)`. This presents challenges because Ruby's GC is incremental, meaning that if the `ptr` changes (e.g. realloc'd or free'd), then we could have an invalid memory access. This also overwrites `*ptr = Qundef` if `*ptr` is dead, which prevents any cleanup to be run (e.g. freeing memory or deleting entries from hash tables). This ticket proposes `rb_gc_declare_weak_references` which declares that an object has weak references and calls a cleanup function after marking, allowing the object to clean up any memory for dead objects. # Introduction In [[Feature #19783]](https://bugs.ruby-lang.org/issues/19783), I introduced an API allowing objects to mark weak references, the function signature looks like this: ```c void rb_gc_mark_weak(VALUE *ptr); ``` `rb_gc_mark_weak` is called during the marking phase of the GC to specify that the memory at `ptr` holds a pointer to a Ruby object that is weakly referenced. `rb_gc_mark_weak` appends this pointer to a list that is processed after the marking phase of the GC. If the object at `*ptr` is no longer alive, then it overwrites the object reference with a special value (`*ptr = Qundef`). However, this API resulted in two challenges: 1. Ruby's default GC is incremental, which means that the GC is not ran in one phase, but rather split into chunks of work that interleaves with Ruby execution. The `ptr` passed into `rb_gc_mark_weak` could be on the malloc heap, and that memory could be realloc'd or even free'd. We had to use workarounds such as `rb_gc_remove_weak` to ensure that there were no illegal memory accesses. This made `rb_gc_mark_weak` difficult to use, impacted runtime performance, and increased memory usage. 2. When an object dies, `rb_gc_mark_weak` only overwites the reference with `Qundef`. This means that if we want to do any cleanup (e.g. free a piece of memory or delete a hash table entry), we could not do that and had to defer this process elsewhere (e.g. during marking or runtime). In this ticket, I'm proposing a new API for weak references. Instead of an object marking its weak references during the marking phase, the object declares that it has weak references using the `rb_gc_declare_weak_references` function. This declaration occurs during runtime (e.g. after the object has been created) rather than during GC. After an object declares that it has weak references, it will have its callback function called after marking as long as that object is alive. This callback function can then call a special function `rb_gc_handle_weak_references_alive_p` to determine whether its references are alive. This will allow the callback function to do whatever it wants on the object, allowing it to perform any cleanup work it needs. This significantly simplifies the code for `ObjectSpace::WeakMap` and `ObjectSpace::WeakKeyMap` because it no longer needs to have the workarounds for the limitations of `rb_gc_mark_weak`. # Performance The performance results below demonstrate that `ObjectSpace::WeakMap#[]=` is now about 60% faster because the implementation has been simplified and the number of allocations has been reduced. We can see that there is not a significant impact on the performance of `ObjectSpace::WeakMap#[]`. Base: ``` ObjectSpace::WeakMap#[]= 4.620M (± 6.4%) i/s (216.44 ns/i) - 23.342M in 5.072149s ObjectSpace::WeakMap#[] 30.967M (± 1.9%) i/s (32.29 ns/i) - 154.998M in 5.007157s ``` Branch: ``` ObjectSpace::WeakMap#[]= 7.336M (± 2.8%) i/s (136.31 ns/i) - 36.755M in 5.013983s ObjectSpace::WeakMap#[] 30.902M (± 5.4%) i/s (32.36 ns/i) - 155.901M in 5.064060s ``` Code: ``` require "bundler/inline" gemfile do source "https://rubygems.org" gem "benchmark-ips" end wmap = ObjectSpace::WeakMap.new key = Object.new val = Object.new wmap[key] = val Benchmark.ips do |x| x.report("ObjectSpace::WeakMap#[]=") do |times| i = 0 while i < times wmap[Object.new] = Object.new i += 1 end end x.report("ObjectSpace::WeakMap#[]") do |times| i = 0 while i < times wmap[key] wmap[val] # does not exist i += 1 end end end ``` # Alternative designs Currently, `rb_gc_declare_weak_references` is designed to be an internal-only API. This allows us to assume the object types that call `rb_gc_declare_weak_references`. In the future, if we want to open up this API to third parties, we may want to change this function to something like: ```c void rb_gc_add_cleaner(VALUE obj, void (*callback)(VALUE obj)); ``` This will allow the third party to implement a custom `callback` that gets called after the marking phase of GC to clean up any dead references. I chose not to implement this design because it is less efficient as we would need to store a mapping from `obj` to `callback`, which requires extra memory.
2025-12-23Move special const check to gc.c for rb_gc_impl_object_moved_pPeter Zhu
2025-12-22[ruby/mmtk] Implement Ruby heapPeter Zhu
This heap emulates the growth characteristics of the Ruby default GC's heap. By default, the heap grows by 40%, requires at least 20% empty after a GC, and allows at most 65% empty before it shrinks the heap. This is all configurable via the same environment variables the default GC uses (`RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO`, `RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO`, `RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO`, respectively). The Ruby heap can be enabled via the `MMTK_HEAP_MODE=ruby` environment variable. Compared to the dynamic heap in MMTk (which uses the MemBalancer algorithm), the Ruby heap allows the heap to grow more generously, which uses a bit more memory but offers significant performance gains because it runs GC much less frequently. We can see in the benchmarks below that this Ruby heap heap gives faster performance than the dynamic heap in every benchmark, with over 2x faster in many of them. We see that memory is often around 10-20% higher with certain outliers that use significantly more memory like hexapdf and erubi-rails. We can also see that this brings MMTk's Ruby heap much closer in performance to the default GC. Ruby heap benchmark results: -------------- -------------- ---------- --------- bench ruby heap (ms) stddev (%) RSS (MiB) activerecord 233.6 10.7 85.9 chunky-png 457.1 1.1 79.3 erubi-rails 1148.0 3.8 133.3 hexapdf 1570.5 2.4 403.0 liquid-c 42.8 5.3 43.4 liquid-compile 41.3 7.6 52.6 liquid-render 102.8 3.8 55.3 lobsters 651.9 8.0 426.3 mail 106.4 1.8 67.2 psych-load 1552.1 0.8 43.4 railsbench 1707.2 6.0 145.6 rubocop 127.2 15.3 148.8 ruby-lsp 136.6 11.7 113.7 sequel 47.2 5.9 44.4 shipit 1197.5 3.6 301.0 -------------- -------------- ---------- --------- Dynamic heap benchmark results: -------------- ----------------- ---------- --------- bench dynamic heap (ms) stddev (%) RSS (MiB) activerecord 845.3 3.1 76.7 chunky-png 525.9 0.4 38.9 erubi-rails 2694.9 3.4 115.8 hexapdf 2344.8 5.6 164.9 liquid-c 73.7 5.0 40.5 liquid-compile 107.1 6.8 40.3 liquid-render 147.2 1.7 39.5 lobsters 697.6 4.5 342.0 mail 224.6 2.1 64.0 psych-load 4326.7 0.6 37.4 railsbench 3218.0 5.5 124.7 rubocop 203.6 6.1 110.9 ruby-lsp 350.7 3.2 79.0 sequel 121.8 2.5 39.6 shipit 1510.1 3.1 220.8 -------------- ----------------- ---------- --------- Default GC benchmark results: -------------- --------------- ---------- --------- bench default GC (ms) stddev (%) RSS (MiB) activerecord 148.4 0.6 67.9 chunky-png 440.2 0.7 57.0 erubi-rails 722.7 0.3 97.8 hexapdf 1466.2 1.7 254.3 liquid-c 32.5 3.6 42.3 liquid-compile 31.2 1.9 35.4 liquid-render 88.3 0.7 30.8 lobsters 633.6 7.0 305.4 mail 76.6 1.6 53.2 psych-load 1166.2 1.3 29.1 railsbench 1262.9 2.3 114.7 rubocop 105.6 0.8 95.4 ruby-lsp 101.6 1.4 75.4 sequel 27.4 1.2 33.1 shipit 1083.1 1.5 163.4 -------------- --------------- ---------- --------- https://github.com/ruby/mmtk/commit/c0ca29922d
2025-12-21[ruby/mmtk] Add a 32 byte heap for allocating smaller objectsPeter Zhu
https://github.com/ruby/mmtk/commit/c4cca6c1c3
2025-12-20[ruby/mmtk] Implement fast path for bump pointer allocatorPeter Zhu
Adding a fast path for bump pointer allocator can improve allocation performance. For the following microbenchmark with MMTK_HEAP_MIN=100MiB: 10_000_000.times { String.new } Before: 810.7 ms ± 8.3 ms [User: 790.9 ms, System: 40.3 ms] After: 777.9 ms ± 10.4 ms [User: 759.0 ms, System: 37.9 ms] https://github.com/ruby/mmtk/commit/0ff5c9f579
2025-12-20[ruby/mmtk] Make rb_gc_impl_heap_id_for_size use MMTK_HEAP_COUNTPeter Zhu
https://github.com/ruby/mmtk/commit/2185189df4
2025-12-20[ruby/mmtk] Call rb_bug when Ruby mutator thread panicsPeter Zhu
This will allow the Ruby backtrace, memory mapping, etc. to be outputted when a Ruby mutator thread panics. https://github.com/ruby/mmtk/commit/d10fd325dd
2025-12-19[ruby/mmtk] Extract max object size to MMTK_MAX_OBJ_SIZEPeter Zhu
https://github.com/ruby/mmtk/commit/ed9036c295
2025-12-19[ruby/mmtk] Extract heap count to MMTK_HEAP_COUNT macroPeter Zhu
https://github.com/ruby/mmtk/commit/4e789e118b
2025-12-16Use RBIMPL_ASSERT_OR_ASSUME instead of ASSUME for better errors when it does ↵Benoit Daloze
not hold
2025-12-16gc.h: Reintroduce immediate guard in `rb_obj_written`Jean Boussier
This guard was removed in https://github.com/ruby/ruby/pull/13497 on the justification that some GC may need to be notified even for immediate. But the two currently available GCs don't, and there are plenty of assumtions GCs don't everywhere, notably in YJIT and ZJIT. This optimization is also not so micro (but not huge either). I routinely see 1-2% wasted there on micro-benchmarks. So perhaps if in the future we actually need this, it might make sense to introduce a way for GCs to declare that as an option, but in the meantime it's extra overhead with little gain.
2025-12-10GC_DEBUG_STRESS_TO_CLASS should only be for debugJohn Hawthorn
I believe this was accidentally left in as part of 2beb3798bac52624c3170138f8ef65869f1da6c0
2025-12-05Revert "gc.c: Pass shape_id to `newobj_init`"Peter Zhu
This reverts commit 228d13f6ed914d1e7f6bd2416e3f5be8283be865. This commit makes default.c and mmtk.c depend on shape.h, which prevents them from building independently.
2025-12-03Track small malloc/free changes in thread localJohn Hawthorn
2025-12-03Group malloc counters togetherJohn Hawthorn
2025-12-03gc.c: Pass shape_id to `newobj_init`Jean Boussier
Attempt to fix the following SEGV: ``` ruby(gc_mark) ../src/gc/default/default.c:4429 ruby(gc_mark_children+0x45) [0x560b380bf8b5] ../src/gc/default/default.c:4625 ruby(gc_mark_stacked_objects) ../src/gc/default/default.c:4647 ruby(gc_mark_stacked_objects_all) ../src/gc/default/default.c:4685 ruby(gc_marks_rest) ../src/gc/default/default.c:5707 ruby(gc_marks+0x4e7) [0x560b380c41c1] ../src/gc/default/default.c:5821 ruby(gc_start) ../src/gc/default/default.c:6502 ruby(heap_prepare+0xa4) [0x560b380c4efc] ../src/gc/default/default.c:2074 ruby(heap_next_free_page) ../src/gc/default/default.c:2289 ruby(newobj_cache_miss) ../src/gc/default/default.c:2396 ruby(RB_SPECIAL_CONST_P+0x0) [0x560b380c5df4] ../src/gc/default/default.c:2420 ruby(RB_BUILTIN_TYPE) ../src/include/ruby/internal/value_type.h:184 ruby(newobj_init) ../src/gc/default/default.c:2136 ruby(rb_gc_impl_new_obj) ../src/gc/default/default.c:2500 ruby(newobj_of) ../src/gc.c:996 ruby(rb_imemo_new+0x37) [0x560b380d8bed] ../src/imemo.c:46 ruby(imemo_fields_new) ../src/imemo.c:105 ruby(rb_imemo_fields_new) ../src/imemo.c:120 ``` I have no reproduction, but my understanding based on the backtrace and error is that GC is triggered inside `newobj_init` causing the new object to be marked while in a incomplete state. I believe the fix is to pass the `shape_id` down to `newobj_init` so it can be set before the GC has a chance to trigger.
2025-12-01Add remembered flag to heap dumpJohn Hawthorn
This should be less common than than many of the other flags, so should not inflate the heap too much. This is desirable because reducing the number of remembered objects will improve minor GC speeds.
2025-11-30Compare with the upper bound of the loop variableNobuyoshi Nakada
Fix sign-compare warning
2025-11-27Fix for modgcJohn Hawthorn
2025-11-27Mask off unused VWA bitsJohn Hawthorn
2025-11-26Set age bitmap outside of adding to freelistJohn Hawthorn
This allows us to do less work when allocating a fresh page.
2025-11-26Avoid extra set of age bit flagsJohn Hawthorn
2025-11-25Fix live object count for multi-Ractor forkingPeter Zhu
Since we do not run a Ractor barrier before forking, it's possible that another other Ractor is halfway through allocating an object during forking. This may lead to allocated_objects_count being off by one. For example, the following script reproduces the bug: 100.times do |i| Ractor.new(i) do |j| 10000.times do |i| "#{j}-#{i}" end Ractor.receive end pid = fork { GC.verify_internal_consistency } _, status = Process.waitpid2 pid raise unless status.success? end We need to run with `taskset -c 1` to force it to use a single CPU core to more consistenly reproduce the bug: heap_pages_final_slots: 1, total_freed_objects: 16628 test.rb:8: [BUG] inconsistent live slot number: expect 19589, but 19588. ruby 4.0.0dev (2025-11-25T03:06:55Z master 55892f5994) +PRISM [x86_64-linux] -- Control frame information ----------------------------------------------- c:0007 p:---- s:0029 e:000028 l:y b:---- CFUNC :verify_internal_consistency c:0006 p:0004 s:0025 e:000024 l:n b:---- BLOCK test.rb:8 [FINISH] c:0005 p:---- s:0022 e:000021 l:y b:---- CFUNC :fork c:0004 p:0012 s:0018 E:0014c0 l:n b:---- BLOCK test.rb:8 c:0003 p:0024 s:0011 e:000010 l:y b:0001 METHOD <internal:numeric>:257 c:0002 p:0005 s:0006 E:001730 l:n b:---- EVAL test.rb:1 [FINISH] c:0001 p:0000 s:0003 E:001d20 l:y b:---- DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- test.rb:1:in '<main>' <internal:numeric>:257:in 'times' test.rb:8:in 'block in <main>' test.rb:8:in 'fork' test.rb:8:in 'block (2 levels) in <main>' test.rb:8:in 'verify_internal_consistency' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- ruby(rb_print_backtrace+0x14) [0x61b67ac48b60] vm_dump.c:1105 ruby(rb_vm_bugreport) vm_dump.c:1450 ruby(rb_bug_without_die_internal+0x5f) [0x61b67a818a28] error.c:1098 ruby(rb_bug) error.c:1116 ruby(gc_verify_internal_consistency_+0xbdd) [0x61b67a83d8ed] gc/default/default.c:5186 ruby(gc_verify_internal_consistency+0x2d) [0x61b67a83d960] gc/default/default.c:5241 ruby(rb_gc_verify_internal_consistency) gc/default/default.c:8950 ruby(gc_verify_internal_consistency_m) gc/default/default.c:8966 ruby(vm_call_cfunc_with_frame_+0x10d) [0x61b67a9e50fd] vm_insnhelper.c:3902 ruby(vm_sendish+0x111) [0x61b67a9eeaf1] vm_insnhelper.c:6124 ruby(vm_exec_core+0x84) [0x61b67aa07434] insns.def:903 ruby(vm_exec_loop+0xa) [0x61b67a9f8155] vm.c:2811 ruby(rb_vm_exec) vm.c:2787 ruby(vm_yield_with_cref+0x90) [0x61b67a9fd2ea] vm.c:1865 ruby(vm_yield) vm.c:1873 ruby(rb_yield) vm_eval.c:1362 ruby(rb_protect+0xef) [0x61b67a81fe6f] eval.c:1154 ruby(rb_f_fork+0x16) [0x61b67a8e98ab] process.c:4293 ruby(rb_f_fork) process.c:4284
2025-11-24Fix style for rb_gc_impl_after_forkPeter Zhu