From 1486031ba04a14466be6071663b13e549f9381ca Mon Sep 17 00:00:00 2001 From: Nick Barnes Date: Tue, 4 Jun 2013 15:28:02 +0100 Subject: [PATCH] Check in incomplete strategy design doc. Copied from Perforce Change: 182491 ServerID: perforce.ravenbrook.com --- mps/design/strategy.txt | 466 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 mps/design/strategy.txt diff --git a/mps/design/strategy.txt b/mps/design/strategy.txt new file mode 100644 index 00000000000..129094b8f6c --- /dev/null +++ b/mps/design/strategy.txt @@ -0,0 +1,466 @@ +.. mode: -*- rst -*- + +MPS Strategy +============ + +:Tag: design.mps.strategy +:Author: Nick Barnes +:Organization: Ravenbrook Limited +:Date: 2013-06-04 + :Revision: $Id$ : //info.ravenbrook.com/project/mps/master/design/txt#8 $ +:Copyright: See section `Copyright and License`_. + + +.. sources: + + ``_ + +.. mps:prefix:: design.mps.strategy + +Introduction +------------ + +_`.intro` This is the design of collection strategy for the MPS. + +_`.readership` MPS developers. + + +Overview +-------- + +_`.overview` The MPS uses "strategy" code to make three decisions: + +- when to start a collection trace; + +- what to condemn; + +- how to schedule tracing work. + +This document describes the current strategy, identifies some +weaknesses in it, and outlines some possible future development +directions. + + +Requirements +------------ + +[We could source some from req.dylan, or do an up-to-date requirements +analysis -- NB 2013-03-25] + +Garbage collection is a trade-off between time and space: it consumes +some [CPU] time in order to save some [memory] space. Strategy shifts +the balance point. A better strategy will take less time to produce +more space. Examples of good strategy might include: + +- choosing segments to condemn which contain high proportions of dead + objects; + +- starting a trace when a large number of objects have just died; + +- doing enough collection soon enough that the client program never + suffers low-memory problems; + +- using otherwise-idle CPU resources for tracing. + +Conversely, it would be bad strategy to do the reverse of each of +these (condemning live objects; tracing when there's very little +garbage; not collecting enough; tracing when the client program is +busy). + +Abstracting from these notions, requirements on strategy would +relate to: + +- Maximum pause time and other utilization metrics (for example, + bounded mutator utilization, minimum mutator utilization, total MPS + CPU usage); + +- Collecting enough garbage (for example: overall heap size; + low-memory requirements). + +- Allowing client control (for example, client recommendations for + collection timing or condemnation). + +There are other possible strategy considerations which are so far +outside the scope of current strategy and MPS design that this +document disregards them. For example, either inferring or allowing +the client to specify preferred relative object locations ("this +object should be kept in the same cache line as that one"), to improve +cache locality. + +Generations +----------- + +The largest part of the current MPS strategy implementation is the +support for generational GC. Generations are only fully supported for +AMC (and AMCZ) pools. See under "Non-AMC Pools", below, for more +information. + +Data Structures +............... + +The fundamental structure of generational GC is the ``Chain``, +which describes a set of generations. A chain is created by client +code calling ``mps_chain_create()``, specifying the "size" and +"mortality" for each generation. When creating an AMC pool, the +client code must specify the chain which will control collections for +that pool. The same chain may be used for multiple pools. + +Each generation in a chain has a ``GenDesc`` structure, +allocated in an array pointed to from the chain. Each AMC pool has a +set of ``PoolGen`` structures, one per generation. The PoolGens +for each generation point to the GenDesc and are linked together in a +ring on the GenDesc. These structures are (solely?) used to gather +information for strategy decisions. + +The arena has a unique ``GenDesc`` structure, named +``topGen`` and described in comments as "the dynamic generation" +(although in fact it is the *least* dynamic generation). Each AMC +pool has one more PoolGen than there are GenDescs in the chain. The +extra PoolGen refers to this topGen. + +AMC segments have a segment descriptor ``amcSegStruct`` which is +a ``GCSegStruct`` with two additional fields. One field +``segTypeP`` is a pointer either to the per-generation per-pool +``amcGen`` structure (a subclass of ``PoolGen``), or to a +nailboard (which then points to an amcGen). The other field +``new`` is a boolean used for keeping track of memory usage for +strategy reasons (see below under 'Accounting'). The ``amcGen`` +is used for statistics (``->segs``) and forwarding buffers +(``->forward``). + +The AMC pool class only ever allocates a segment in order to fill a +buffer: either the buffer for a client Allocation Point, or a +forwarding buffer. In order to support generational collection, there +is a subclass ``amcBuf`` of ``SegBuf``, with a +``gen`` field (pointing to a ``amcGen``). So in +``AMCBufferFill()`` the generation of the new segment can be +determined. + +When an AMC pool is created, these ``amcGen`` and +``amcBuf`` structures are all created, and the +``amcBuf->gen`` fields initialized so that the forwarding buffer +of each amcGen knows that it belongs to the next "older" amcGen (apart +from the "oldest" amcGen - that which refers to the topGen - whose +forwarding buffer belongs to itself). + +When copying an object in ``AMCFix()``, the object's current +generation is determined (``amcSegGen()``), and the object is +copied to that amcGen's forwarding buffer, using the buffer protocol. +Thus, objects are "promoted" up the chain of generations until they +end up in the topGen, which is shared between all chains and all +pools. + +For statistics and reporting purposes, when ``STATISTICS`` is +on, each AMC pool has an array of ``PageRetStruct``s, one per +trace. This structure has many ``Count`` fields, and is +intended to help to assess AMC page retention code. See job001811. + +Zones +..... + +All collections in the MPS start with condemnation of a complete +``ZoneSet``. Each generation in each chain has a zoneset +associated with it (``chain->gen[N].zones``); the condemned +zoneset is the union of some number of generation's zonesets. It is +condemned by code in the chain system calling +``TraceCondemnZones()``. This is either for all chains +(``ChainCondemnAll()`` called for every chain from +``traceCondemnAll()``) or for some number of generations in a +single chain (``ChainCondemnAuto()`` called from +``TracePoll()``). Note that the condemnation is of every +automatic-pool segment in any zone in the zoneset. It is not limited +to the segments actually associated with the condemned generation(s). + +An attempt is made to use distinct zonesets for different generations. +Whenever a segment is allocated (``AMCBufferFill()``), a +``SegPref`` is created containing the generation number +(obtained from ``amcBuf->gen->pgen->nr``) and passed to +``SegAlloc()``. The arena keeps a zoneset for each generation +number (up to ``VMArenaGenCount``, defined in +``arenavm.c`` to be ``MPS_WORD_WIDTH/2``), and a +``freeSet``. The zoneset for each generation number starts out +empty, and the ``freeSet`` starts out ``ZoneSetUNIV``. +When a segment is allocated with a ``SegPref`` with a generation +number, an attempt is made to allocate it in the corresponding zoneset +(``pagesFindFreeInZones()``). If the zoneset is empty, an +attempt is made to allocate it in the ``freeSet`` zoneset. +After it is allocated, the zones it occupies are removed from the +``freeSet`` and (if there's a generation ``SegPref``) +added to the zoneset for that generation number. + +Note that this zone placement code knows nothing of chains, +generations, pool classes, etc. It is based solely on the generation +*number*, so generations with the same number from different chains +share a zoneset preference for the purpose of placing newly allocated +segments. Combined with the fact that condemnation is per-zone, this +effectively means that generations in distinct chains are collected +together. One consequence of this is that we don't have a very fine +granularity of control over collection: a garbage collection of all +chains together is triggered by the most eager chain. There's no way +for a library or other small part of a client program to arrange +independent collection of a separate pool or chain. + +When ``AMCBufferFill()`` gets the allocated segment back, it +adds it to the zoneset associated with that generation in the pool's +controlling chain. Note that a chain's per-generation zonesets, which +represent the zones in which segments for that generation in that +chain have been placed, are quite distinct from the arena-wide +per-generation-number zonesets, which represent the zones in which +segments for that generation number in any chain have been placed. +The arena-wide per-generation-number zoneset +``vmArena->genZoneSet[N]`` is augmented in +``vmAllocComm()``. The per-chain per-generation zoneset +``chain->gen[N].zones`` is augmented in +``PoolGenUpdateZones()``. Neither kind of zoneset can ever +shrink. + +Accounting +.......... + +Each PoolGen + +gen[N].proflow ?? + +gen[N].mortality +gen[N].capacity + +pgen->totalSize +pgen->newSize +pgen->newSizeAtCreate + +amcSeg->new + + +pgen->totalSize incremented by AMCBufferFill(), decremented by +amcReclaimNailed() and AMCReclaim(), added up by +GenDescTotalSize(gen). + +pgen->newSize incremented by AMCBufferFill() (*when not ramping*) and +AMCRampEnd(), decremented by AMCWhiten(). + +gen[N].proflow set to 1.0 by ChainCreate(). arena->topGen.proflow set +to 0.0 by LocusInit(arena). This field is never used. + +added up by GenDescNewSize(gen). + + + + +pgen->newSizeAtCreate is set by traceCopySizes() (that is its +purpose), and output by TraceStartGenDesc_diag(). + + + + + + + + +Ramps +..... +The intended semantics of ramping are pretty simple. It allows the +client to advise us of periods of large short-lived allocation on a +particular AP. Stuff allocated using that AP during its "ramp" will +probably be dead when the ramp finishes. How the MPS makes use of this +advice is up to us, but for instance we might segregate those objects, +collect them less enthusiastically during the ramp and then more +enthusiastically soon after the ramp finishes. Ramps can nest. + +A ramp is entered by calling:: + + mps_ap_alloc_pattern_begin(ap, mps_alloc_pattern_ramp()) + +or similar, and left in a similar way. + +This is implemented on a per-pool basis, for AMC only (it's ignored by +the other automatic pools). PoolAMC throws away the identity of the AP +specified by the client. The implementation is intended to work by +changing the generational forwarding behaviour, so that there is a "ramp +generation" - one of the regular AMC generations - which forwards to +itself if collected during a ramp (instead of promoting to an older +generation). It also tweaks the strategy calculation code, in a way +with consequences I am documenting elsewhere. + +Right now, the code sets this ramp generation to the last generation +specified in the pool's "chain": it ordinarily forwards to the +"after-ramp" generation, which is the "dynamic generation" (i.e. the +least dynamic generation, i.e. the arena-wide "top generation"). My +recollection, and some mentions in design/poolamc, suggests that the +ramp generation used to be chosen differently from this. + +So far, it doesn't sound too ghastly, I guess, although the subversion +of the generational system seems a little daft. Read on.... + +An AMC pool has a ``rampMode`` (which is really a state of a state +machine), taking one of five values: OUTSIDE, BEGIN, RAMPING, FINISH, +and COLLECTING (actually the enum values are called RampX for these +X). We initialize in OUTSIDE. The pool also has a ``rampCount``, +which is the ramp nesting depth and is used to allow us to ignore ramp +transitions other than the outermost. According to design/poolamc, +there's an invariant (in BEGIN or RAMPING, ``rampCount > 0``; in +COLLECTING or OUTSIDE, ``rampCount == 0``), but this isn't checked in +``AMCCheck()`` and in fact is false for COLLECTING (see below). + +There is a small set of events causing state machine transitions: + +- entering an outermost ramp; +- leaving an outermost ramp; +- condemning any segment of a ramp generation (detected in AMCWhiten); +- reclaiming any AMC segment. + +Here's pseudo-code for all the transition events: + +Entering an outermost ramp: + if not FINISH, go to BEGIN. + +Leaving an outermost ramp: + if RAMPING, go to FINISH. Otherwise, go to OUTSIDE. + +Condemning a ramp generation segment: + If BEGIN, go to RAMPING and make the ramp generation forward + to itself (detach the forwarding buffer and reset its generation). + If FINISH, go to COLLECTING and make the ramp generation + forward to the after-ramp generation. + +Reclaiming any AMC segment: + If COLLECTING: + if ``rampCount > 0``, go to BEGIN. Otherwise go to OUTSIDE. + +Now, some deductions: + +1. When OUTSIDE, the count is always zero, because (a) it starts that +way, and the only ways to go OUTSIDE are (b) by leaving an outermost +ramp (count goes to zero) or (c) by reclaiming when the count is zero. + +2. When BEGIN, the count is never zero (consider the transitions to +BEGIN and the transition to zero). + +3. When RAMPING, the count is never zero (again consider transitions to +RAMPING and the transition to zero). + +4. When FINISH, the count can be anything (the transition to FINISH has +zero count, but the Enter transition when FINISH can change that and +then it can increment to any value). + +5. When COLLECTING, the count can be anything (from the previous fact, +and the transition to COLLECTING). + +6. **** THE BUG **** the ramp generation is not always reset (to forward +to the after-ramp generation). If we get into FINISH and then see +another ramp before the next condemnation of the ramp generation, we +will Enter followed by Leave. The Enter will keep us in FINISH, and the +Leave will take us back to OUTSIDE, skipping the transition to the +COLLECTING state which is what resets the ramp generation forwarding +buffer. + +The simplest change to fix this is to change the behaviour of the Leave +transition, which should only take us OUTSIDE if we are in BEGIN or +COLLECTING. We should also update design/poolamc to tell the truth, and +check the invariants, which will be these: + + OUTSIDE => zero + BEGIN => non-zero + RAMPING => non-zero + +A cleverer change might radically rearrange the state machine +(e.g. reduce the number of states to three) but that would require +closer design thought and should probably be postponed until we have a +clearer overall strategy plan. + +While I'm writing pseudo-code versions of ramp-related code, I should +mention this other snippet, which is the only other code relating to +ramping (these notes are useful when thinking about the broader strategy +code): + +- In ``AMCBufferFill()``, if we're RAMPING, and filling the forwarding +buffer of the ramp generation, and the ramp generation is the +forwarding buffer's generation, set ``amcSeg->new`` to FALSE. Otherwise, +add the segment size to ``poolGen.newSize``. + +And since I've now mentioned the ``amcSeg->new`` flag, here are the only +other uses of that: + +- it initializes as TRUE. + +- When leaving an outermost ramp, go through all the segments in the +pool. Any non-white segment in the rampGen with new set to FALSE has +its size added to ``poolGen->newSize`` and gets new set to TRUE. + +- in ``AMCWhiten()``, if new is TRUE, the segment size is deducted +from ``poolGen.newSize`` and new is set to FALSE. + +Non-AMC Pools +............. + +The implementations of AMS, AWL, and LO pool classes are all aware of +generations (this is necessary because all tracing is driven by the +generational data structures described above), but do not make use of +them. For LO and AWL, when a pool is created, a chain with a single +generation is also created, with size and mortality parameters +hard-wired into the pool-creation function (LOInit, AWLInit). For +AMS, a chain is passed as a pool creation parameter into +``mps_pool_create()``, but this chain must also have only a +single generation (otherwise ``ResPARAM`` is returned). + +Note that these chains are separate from any chain used by an AMC pool +(except in the trivial case when a single-generation chain is used for +both AMC and AMS). Note also that these pools do not use or point to +the ``arena->topGen``, which applies only to AMC. + +Non-AMC pools have no support for ramps. + +Starting a Trace +................ + +Trace Progress +.............. + +Document History +---------------- +- 2013-06-04 NB Checked this in although it's far from complete. + Pasted in my 'ramping notes' from email, which mention some bugs + which I may have fixed (TODO: check this). + +.. _NB: http://www.ravenbrook.com/consultants/nb/ + + +Copyright and License +--------------------- +Copyright © 2013 Ravenbrook Limited. All rights reserved. +. This is an open source license. Contact +Ravenbrook for commercial licensing options. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Redistributions in any form must be accompanied by information on how +to obtain complete source code for this software and any +accompanying software that uses this software. The source code must +either be included in the distribution or be available for no more than +the cost of distribution plus a nominal fee, and must be freely +redistributable under reasonable conditions. For an executable file, +complete source code means the source code for all modules it contains. +It does not include source code for modules or files that typically +accompany the major components of the operating system on which the +executable file runs. + +**This software is provided by the copyright holders and contributors +"as is" and any express or implied warranties, including, but not +limited to, the implied warranties of merchantability, fitness for a +particular purpose, or non-infringement, are disclaimed. In no event +shall the copyright holders and contributors be liable for any direct, +indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or +services; loss of use, data, or profits; or business interruption) +however caused and on any theory of liability, whether in contract, +strict liability, or tort (including negligence or otherwise) arising in +any way out of the use of this software, even if advised of the +possibility of such damage.**