(READY FOR REVIEW)Garbage collect: A stop-the-world cycle collector#4180
(READY FOR REVIEW)Garbage collect: A stop-the-world cycle collector#4180discord9 wants to merge 4 commits into
Conversation
|
A simple example of collecting cycle ref of PyList(note the line of collect cyclic garbage composed of two object( |
Sorry, something went wrong.
|
Rust 1.64 came out a few hours ago, so there are some new compilation warnings and Clippy lints to deal with in a separate PR (#4182). Once that PR is merged, it might be a good idea to rebase this branch on top of a fresh copy of |
Sorry, something went wrong.
|
Just realize I still need make some change to make gc adopt to threading, need to think about it. |
Sorry, something went wrong.
|
bug: might need to store the number of PyWeak ref count to prevent premature dealloc in heap and access violation. Also PyWeak itself is storing a PyObjectRef which keep the object from drop, some change need to be made to change that |
Sorry, something went wrong.
CPython has a generational garbage collector with a total of three generations. The first generation has a threshold of 700 objects, and the next two have a threshold of 10 objects each. 1 I imagine that bumping the current threshold from 100 up to 700 objects may mitigate some of the performance decrease you're currently observing. Footnotes |
Sorry, something went wrong.
|
Sorry for the messy commit logs, will reword them later. |
Sorry, something went wrong.
|
When happens when you compile this with the |
Sorry, something went wrong.
|
Yes, just realize that, gonna fix it, also still debugging on the dead lock&double dealloc issue, my code is still very faulty |
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
|
Might found a bug related to the old ref count system: #4238 ,still trying to find a fix |
Sorry, something went wrong.
|
Using PyRwLock is indeed very slow, might need to found out a way to trace PyMutex properly |
Sorry, something went wrong.
|
I've slightly taken a peak at the paper this is based on, I'm guessing the two improvements it mentions have been implemented, right? I.e, using the buffered flag to not re-add it to the collector list and checking the current items in the collector as a single graph and not as distinct components. |
Sorry, something went wrong.
Yes, this is the main improvement here, I write a buffered field for every object and there is a roots vec, actually the name of methods is corresponding between paper and the actual code |
Sorry, something went wrong.
There was a problem hiding this comment.
Thank you so much for working on this! I started this a week or 2 ago and then it slipped my mind after I took a break about halfway through looking at stuff. I haven't looked at the actual gc code, and though I'd really like to, to be honest I'm not sure I'll have the bandwidth to do so. If no other maintainers feel able to either, worst case we mark it as a sort of experimental feature, but the test suite passing fine gives me confidence that it's working more or less as it should. It's not like there haven't already been multithreaded gc issues even without the tracing gc, lol.
Sorry, something went wrong.
|
Maybe we can create a separate CI check that runs the GC with a select few python test files? The idea is to at least have it in and running relevant tests on it while not slowing down the total runtime. |
Sorry, something went wrong.
There was a problem hiding this comment.
I am really sorry for late review.
I am not understanding how this gc is working - and I seem to not have enough time to get it before finish review - but I'd like to merge it as an optional feature and a good starting point to enhance gc better in future.
But before merging this change, I hope to minimize changes of CPython-originated code because changed CPython code will be easily forgotten from future developers and it even might be broken when the future enhancement is correct but not compatible with this change.
Sorry, something went wrong.
I am thinking about update document on how this GC works, perhaps even add it to documents of RustPython? |
Sorry, something went wrong.
|
That will be great in future. I think separating gc feature and reduce effect to non-gc build as it was will be good enough for now. |
Sorry, something went wrong.
|
i rebased it on main branch |
Sorry, something went wrong.
I will fix the CI |
Sorry, something went wrong.
de08f82 to
0c11636
Compare
March 28, 2023 15:36
feat: add double drop check in debug mode fix: use Mutex instead of PyMutex in `ID2TYPE` refactor: cfg cond for `Drop` trait instead feat: add `Trace` trait feat: trace RwLock right&trace tuple feat: `Trace` for `PyDict` feat: `Trace` on `PyIter`&`PyIterReturn`&`PyIterIter` feat: `Trace` on PyEnumerate feat: `Trace` on `ArgCallable` `ArgIterable` `ArgMapping` `ArgSequence` feat: `Trace` on `IterStatus` `PySequenceIterator` `PyCallableIterator` `PositionIterInternal` feat: `Trace` on `PyReverseSequenceIterator` feat: `Trace` on `PyTuple` `PyTupleIterator` `PyTupleTyped` feat: `Trace` on `PyFilter` `PyFunction` `PyBoundMethod` feat: `Trace` on `PyCell` feat: `Trace` on `PyList` `PyListIterator` `PyListReverseIterator` feat: `Trace` on `PyMap` `PyMappingProxy` `MappingProxyInner` feat: `Trace` on PyMemoryViewNewArgs, PyMemoryViewIterator feat: `Trace` on PyProperty, PySet, PySetInner feat: `Trace` on PySlice, PyStaticMethod feat: `Trace` on FuncArgs, KwArgs, PosArgs, OptionalArg feat: `Trace` on PySuper, PySuperNewArgs feat: `Trace` on `PyTraceback` feat: `Trace` for PyBaseException, PyType, PyUnion feat: `Trace` on PyWeakProxy, PyZip, PyBuffer feat: `Trace` on PyMapping, PyNumber, PySequence feat: add `list_traceable` macro fix: right lifetime for `TracerFn` feat: `trace` PyObjectRef&PyRef feat: garbage cycle collector fix: put drop_only in different loop feat: core algorithm of garbage collect feat: add drop_only&dealloc_only to vtable feat: modify core.rs to use gc style: cargo fmt feat: add `try_gc`& gc per frame feat: add `collect` in gc module fix: set black when safe_inc fix: check if is gc-ing in `should_gc` refactor: cfg(gc) for `Drop` trait fix: not add to roots multiple times fix: add judge for if dropped doc: add TODO fix: prevent dealloc cycle garbage early fix: `partially_drop` header later fix: add dealloc guard for deref fix: run `__del__`&drop separately feat: more lock to gc&drop check feat: make gc less freq fix: cfg compile&support attr in partially_drop feat: `pytrace` macro feat: use `#[pytrace]` in some types feat: compact header feat: change gc cond to 10007 obj cnts fix: trace `PyRange` fix: drop ref vtable before dealloc to prevent UB fix: debug check&cfg cond&clippy fix: add ref only after `__del__` is done feat: trace(unsafely ) PyMutex feat: prevent trace PyMutex when not gc feat: change `PyRwlock` back to `PyMutex` fix: testcase test_reference_loop test_unique_composite refactor: early exit of collect_cycles fix: cfg cond feat: gc pause warn msg when wait too long fix: not run __del__ in cycles fix: expected failure for test_unique_composite fix: allow test_ioctl_signed_unsigned_code_param feat: split `drop` to `del`&`weakref` fix: lock cond fix: pause cond so high freq gc not halt all refactor: put impl Collector together feat: drop weak_list later feat: unlock gc pause lock fairly feat: print progress for two long test feat: adjust lock order¬ panic fix: let obj handle its weakref's dealloc fix: check stack before pop fix: not leak weakref log: remove some false alarm fix: cfg flag for cond compile fix: cfg flag for no-default-feature fix: use non-block incref test: change gc to 1ms&exit all if wait too long fix: INCREF done right¬ gc until last gc done feat: add `gc` feature to root crate doc: TODO for PEP442 del in cycle fix: temporaily add one more `gc.collect()` test: add gc feature in CI refactor: make `mark/scan_roots` associated fn refactor: `free_cycles` fn test: flush progress prompt docs: add TODO for modified testcases refactor: header state's type feat: drop_only log: gc info clippy: drop_only allow unused refactor: rename `gc` feature to `gc_bacon` refactor: remove `allow(unused)` feat: `MaybeTrace` trait feat: add `trace` Meta for `pyclass` proc macro feat: cfg cond flag for `MaybeTrace` feat: add `trace` in vtable fix: add`#[pyclass(trace)` for manual impl trace fix: elide null check in vtable&CR refactor fix: change name in CI tests feat: not use gc in wasm refactor: accord to Code Review doc&fix: explain gc&fix macro fix: test_sys_setprofile
|
@discord9 @youknowone do we have some blockers here? Or it's waiting for a rebase and we generally agree on its merge? |
Sorry, something went wrong.
Currently, an old bug stemming from code predating the implementation of garbage collection is causing memory corruption because the garbage collector is indeed collecting that memory. I will open a new pull request once I have fixed those bugs and can reliably pass continuous integration. |
Sorry, something went wrong.
|
@discord9 I am sorry miss the comment. I wish we finally could merge it. If there is any PyTraverse update, could we merge it first? Regardless how GC designed later, it will be useful. |
Sorry, something went wrong.

That might be extend to be a on-the-fly version. Based on #4158
Summary of GC Algorithm
the algorithm store a buffer of object thats' "possible roots" of garbage cycles(i.e. if a object is decrement but not to zero, its a possible root) then, during gc, algorithm have three main phase:
Progress
gc()as a python function(in the stdlib), now invokegc()in python will force a cycle collect to happen, return number of cyclic garbage collected(and freed)PyInner<T>, but I dropPyInner<Erased>, need correct that.(Fixed)gc()to instruction exec loop)PyObjectdirectly instead of dyn dispatch in gcgc()to its' own module in stdlibstdlib::io::_io::BufferedReader(and other types in _io module) keeps double freeheaderlater to prevent UB__del__, drop, dealloc separately to prevent UBheader's most field into one bytefuture features for GC
GC condition
gc will be triggered if the internal buffer(
roots) have exceed 10007(Just a random reasonably large number) element and at least 100ms after last gc, that means after 10007 different objects get decref after last gc, a new gc will be trigger,gc()is only called in the loop of executing instruction, but also reexport agc()to stdlib so one can callgc()from Python side(which will force a gc regardless of above conditon).stop-the-world is impl by try to blocking on a pausing
Mutexowned by gc whenever try to deref PyObjectRef/PyRef/PyBasic ideas
cycle collector algorithm:
Data struct
Example
for code as below:
With and without gc, the memory usage is(run on a win10(19044.2006) with rustc 1.64.0, might vary between different runs):
currently
GcTraced types are: