Add GC.auto_compact / GC.auto_compact=(flag) - #3547
Conversation
|
Would an interface like |
| def self.compact | ||
| Primitive.rb_gc_compact | ||
| Primitive.gc_start_internal true, true, true, true | ||
| Primitive.gc_compact_stats |
There was a problem hiding this comment.
Where does Primitive come from? It's a pretty nice interface.
There was a problem hiding this comment.
This is new in master. I think ko1 introduced it. I'm very excited about Primitive
There was a problem hiding this comment.
FWIW it's from https://bugs.ruby-lang.org/issues/16254 + my suggestion to rename to Primitive.name like in TruffleRuby (and similar in Rubinius before that).
|
I don't fully understand everything that's going on here but I tried to give some basic feedback. If you want to talk duck to me about it, I'm happy to quack through it with you. |
8a4d8fa to
af469bc
Compare
Possibly. I'm going to open a ticket on Redmine so we can discuss the interface there. |
|
I posted a Redmine issue here |
15b31d3 to
361e9f5
Compare
2a665b0 to
63abf9d
Compare
cd9e0ad to
859f673
Compare
6f6234a to
b7a11b9
Compare
c60f527 to
7aba46f
Compare
This commit eliminates the linked list stored in `T_MOVED` and reuses the pinning bits for eliminating `T_MOVED` slots. Instead of writing to the heap, we can reuse the pinning bits to find `T_MOVED` slots. `gc_move` is updated to pin the source slot. The source slot cannot be marked because no object should hold a reference to a `T_MOVED` address after references are updated. Bit truth table: | Mark Bit | Pin Bit | Slot Identity | | :------- | :------ | :------------ | | On | On | Pinned Object | | On | Off | Marked Object | | Off | Off | Garbage | | Off | On | T_MOVED |
This reverts commit 62ce8f9.
Since we combine compact and sweep the free pointer is probably not empty.
The call cache is a weak map and sometimes it can reference classes which will be collected. In the case it points to a collected class, update the class pointer to 0 (an uninitialized call cache)
Some objects that are protected by write barriers want to pin objects. One example is an identity hash. Identity hash is protected by a write barrier, but the keys to the hash cannot move as they are based on memory location. During incremental marking, if a reference is written to one of these objects, the reference is marked via the write barrier even though the mark function on the object would have marked *and* pinned it. To fix this, we will pin every reference that is marked via incremental marking because we cannot know if the object would have pinned its reference
Checking if T_NONE is marked is kind of an error, so lets just check the mark bits directly
If we don't call the marking function on the object, we need to assume the object must be pinned
This commit runs the compactor on every major GC
Compaction only updates references for "live" objects. But it depends on the "before_sweep" flag on a page to figure out which objects are live. If we're compacting, set the before_sweep flag so we don't try to update references on garbage objects
For example, objects that are in the finalizers table. They will be pinned but not marked. Since that is the case, the read barrier should ensure the type is T_MOVED before moving objects back.
We should only update objects on blocks that have live objects. If the objects are live, they won't have ASAN protection, so we should also remove the ASAN calls in order to catch bugs
Marked objects will not be "remarked" after the write barrier executes. Since we can't know whether or not the object would have pinned the reference that was written, we have to take a conservative approach and pin all references written via the write barrier
T_MOVED objects can get pushed on to the stack right before the final compact step. If this happens, the read barriers will get removed before anything has a chance to read from that address. This commit scans the stack when we finish compaction, looking for any moved addresses that happened to get pushed on the stack. If it finds any, it reverts those moves.
Mutating the object id map can cause the map to resize and that can cause a GC. The GC may read from the object id map *while* the map is being mutated, and that isn't safe. This commit disables the GC when mutating the object id map so that the GC won't see the map in an inconsistent state.
7aba46f to
a4e836c
Compare
This commit lets us sort the heap by empty slots so that we can pack objects to the empty side of the heap
fe19215 to
f599003
Compare
This PR adds support for automatic GC compaction on major collections.
GC.enable_autocompactenables the feature andGC.disable_autocompactdisables the feature. The feature is disabled by default.This patch makes object movement happen at the same time as page sweep. When one page finishes sweeping, that page is filled.
Sweep + Move Phase
During sweep, we keep a pointer to the current sweeping page. This pointer is kept in
heap->sweeping_page. At the beginning of sweep, this is the first element of the heap's linked list.At the same time, the compaction process points at the last page in the heap, and that is stored in
heap->compact_cursorhere.Incremental sweeping sweeps one page at a time in the
gc_page_sweepfunction. At the end of that function, we callgc_fill_swept_page.gc_fill_swept_pagefills the page that was just swept and moves the movement cursor towards the sweeping cursor.When the sweeping cursor and the movement cursor meet, sweeping is paused, and references are updated. This can happen in 2 ways, the sweeping cursor "runs in to the moving cursor" which is here. Or the moving cursor runs in to the sweep cursor which happens here.
Either way, the sweep step is paused and references are updated.
Reference Updating
Reference updating hasn't changed, but since reference updating happens before the GC finishes a cycle, it must take in to account garbage objects here.
Read Barrier
During the sweep phase, some objects may touch other objects. For example,
T_CLASSmust remove itself from a parent class.When
Bis freed, it must remove itself fromA's subclasses. But what ifAmoved? To fix this, I've introduced a read barrier. The read barrier protectsheap_page_bodyusingmprotect. If something tries to read from the page, an exception will occur and we can move all objects back to the page (invalidate the movement).The lock function is here.
The unlock function is here.
It uses
sigactionto catch the exception here.Cross Platform
mprotectandsigactionare not cross platform, they doesn't work on Windows. On Windows the read barrier uses exception handlers that are built in to Windows. I implemented them here.The read barrier seems to work on all platforms we're testing.