[pypy-commit] extradoc extradoc: Needs to describe Local GC, at least up to the effect it needs to have
arigo
noreply at buildbot.pypy.org
Tue Aug 21 18:23:56 CEST 2012
Author: Armin Rigo <arigo at tunes.org>
Branch: extradoc
Changeset: r4715:df2afbd7fe5e
Date: 2012-08-21 17:22 +0200
http://bitbucket.org/pypy/extradoc/changeset/df2afbd7fe5e/
Log: Needs to describe Local GC, at least up to the effect it needs to
have on the flags.
diff --git a/talk/stm2012/stmimpl.rst b/talk/stm2012/stmimpl.rst
--- a/talk/stm2012/stmimpl.rst
+++ b/talk/stm2012/stmimpl.rst
@@ -171,7 +171,7 @@
W->h_global = False
W->h_possibly_outdated = False
W->h_written = True
- W->h_revision = 1
+ W->h_revision = 0
return W
@@ -410,12 +410,67 @@
of this explanation we will always assume that it aborts.
+Local garbage collection
+------------------------------------
+
+Before we can commit, we need the system to perform a "local garbage
+collection" step. The problem is that recent objects (obtained with
+``Allocate`` during the transaction) must originally have the
+``h_global`` flag set to False, but this must be changed to True before
+the commit is complete. While we could make a chained list of all such
+objects and change all their ``h_global`` flags now, such an operation
+is wasteful: at least in PyPy, the vast majority of such objects are
+already garbage.
+
+Instead, we describe here the garbage collection mechanism used in PyPy
+(with its STM-specific tweaks). All newly allocated objects during a
+transaction are obtained from a thread-specific "nursery". The nursery
+is empty when the transaction starts. If the nursery fills up during
+the execution of the transaction, a "minor collection" cycle moves the
+surviving objects outside. All these objects, both from the nursery and
+those moved outside, have the ``h_global`` flag set to False.
+
+At the end of the transaction, we perform a "local collection" cycle.
+The main goal is to make surviving objects non-movable --- they cannot
+live in any thread-local nursery as soon as they are visible from other
+threads. If they did, we could no longer clear the content of the
+nursery when it fills up later.
+
+The secondary goal of the local collection is to change the header flags
+of all surviving objects: their ``h_global`` is set to True. As an
+optimization, during this step, all pointers that reference a *local but
+not written to* object are changed to point directly to the original
+global object.
+
+Actual committing occurs after the local collection cycle is complete,
+when *all* reachable objects are ``h_global``.
+
+Hand-wavy pseudo-code::
+
+ def TransactionEnd():
+ FindRootsForLocalCollect()
+ PerformLocalCollect()
+ TransactionCommit() # see below
+
+ def FindRootsForLocalCollect():
+ for (R, L) in global_to_local:
+ if not L->h_written: # non-written local objs are dropped
+ #L->h_revision is already R
+ continue
+ roots.add(R, L, 0) # add 'L' as a root
+
+ def PerformLocalCollect():
+ collect from the roots...
+ for all reached object, change h_global False->True
+ and h_written True->False
+
+
Committing
------------------------------------
Committing is a four-steps process:
-1. We first find all global objects with a local copy that has been
+1. We first take all global objects with a local copy that has been
written to, and mark them "locked" by putting in their ``h_revision``
field a special value that will cause parallel CPUs to spin loop in
``LatestGlobalRevision``.
@@ -456,20 +511,16 @@
``h_revision`` field; it does not involve OS-specific thread locks::
def AcquireLocks():
- for (R, L) in global_to_local:
- if not L->h_written:
- L->h_global = True
- #L->h_revision already points to R
- L->h_possibly_outdated = True
- continue
+ for (R, L, 0) in roots:
v = R->h_revision
if not (v & 1): # "is a pointer", i.e.
AbortTransaction() # "has a more recent revision"
if v >= LOCKED: # already locked by someone else
- spin loop retry # jump back to the "v = ..." line
+ spin loop retry OR # jump back to the "v = ..." line
+ AbortTransaction()
if not CMPXCHG(&R->h_revision, v, my_lock):
spin loop retry # jump back to the "v = ..." line
- locks_acquired.add(R, L, v)
+ save v into the third item in roots, replacing the 0
(Note that for non-written local objects, we skip this locking entirely;
instead, we turn the object into a "global but outdated" object, keeping
@@ -497,8 +548,9 @@
fields::
def AbortTransaction():
- for R, L, v in locks_acquired:
- R->h_revision = v
+ for (R, L, v) in roots:
+ if v != 0:
+ R->h_revision = v
# call longjmp(), which is the function from C
# going back to the transaction start
longjmp()
@@ -511,12 +563,13 @@
def UpdateChainHeads(cur_time):
new_revision = cur_time + 1 # make an odd number
- for (R, L, v) in locks_acquired:
- L->h_global = True
- L->h_written = False
+ for (R, L, v) in roots:
+ #L->h_global is already True
+ #L->h_written is already False
#L->h_possibly_outdated is already False
L->h_revision = new_revision
smp_wmb()
+ #R->h_possibly_outdated is already True
R->h_revision = L
``smp_wmb`` is a "write memory barrier": it means "make sure the
More information about the pypy-commit
mailing list