From 3174eba71eee0e19e2e0eecea83c8dddcefb67de Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Wed, 13 Apr 2016 15:19:50 +0100 Subject: [PATCH] Maintain a moving average of the mortality of each generation. Copied from Perforce Change: 191081 ServerID: perforce.ravenbrook.com --- mps/code/config.h | 9 +- mps/code/eventdef.h | 22 +++-- mps/code/locus.c | 118 ++++++++++++++++++++++--- mps/code/locus.h | 28 ++++-- mps/code/mpmst.h | 2 - mps/code/policy.c | 4 +- mps/code/poolamc.c | 20 +++-- mps/code/poolams.c | 20 +++-- mps/code/poolawl.c | 18 ++-- mps/code/poollo.c | 23 ++--- mps/code/trace.c | 80 ++++++++--------- mps/manual/source/guide/perf.rst | 65 +++++--------- mps/manual/source/release.rst | 16 ++++ mps/manual/source/topic/arena.rst | 6 +- mps/manual/source/topic/collection.rst | 57 +++++------- 15 files changed, 304 insertions(+), 184 deletions(-) diff --git a/mps/code/config.h b/mps/code/config.h index 4bebdfb2d33..ad0c448f493 100644 --- a/mps/code/config.h +++ b/mps/code/config.h @@ -470,7 +470,14 @@ #define VM_ARENA_SIZE_DEFAULT ((Size)1 << 28) -/* Stack configuration -- see */ +/* Locus configuration -- see */ + +/* Weighting for the current observation, in the exponential moving + * average computation of the mortality of a generation. */ +#define LocusMortalityALPHA (0.4) + + +/* Stack probe configuration -- see */ /* Currently StackProbe has a useful implementation only on Windows. */ #if defined(MPS_OS_W3) diff --git a/mps/code/eventdef.h b/mps/code/eventdef.h index 907dbf3520f..75b9ba1c527 100644 --- a/mps/code/eventdef.h +++ b/mps/code/eventdef.h @@ -37,7 +37,7 @@ #define EVENT_VERSION_MAJOR ((unsigned)1) #define EVENT_VERSION_MEDIAN ((unsigned)6) -#define EVENT_VERSION_MINOR ((unsigned)0) +#define EVENT_VERSION_MINOR ((unsigned)1) /* EVENT_LIST -- list of event types and general properties @@ -67,7 +67,7 @@ */ #define EventNameMAX ((size_t)19) -#define EventCodeMAX ((EventCode)0x0087) +#define EventCodeMAX ((EventCode)0x0088) #define EVENT_LIST(EVENT, X) \ /* 0123456789012345678 <- don't exceed without changing EventNameMAX */ \ @@ -189,11 +189,12 @@ EVENT(X, AMCTraceEnd , 0x0081, TRUE, Trace) \ EVENT(X, TraceCreatePoolGen , 0x0082, TRUE, Trace) \ /* new events for performance analysis of large heaps. */ \ - EVENT(X, TraceCondemnZones , 0x0083, TRUE, Trace) \ + /* EVENT(X, TraceCondemnZones , 0x0083, TRUE, Trace) */ \ EVENT(X, ArenaGenZoneAdd , 0x0084, TRUE, Arena) \ EVENT(X, ArenaUseFreeZone , 0x0085, TRUE, Arena) \ /* EVENT(X, ArenaBlacklistZone , 0x0086, TRUE, Arena) */ \ - EVENT(X, PauseTimeSet , 0x0087, TRUE, Arena) + EVENT(X, PauseTimeSet , 0x0087, TRUE, Arena) \ + EVENT(X, TraceEndGen , 0x0088, TRUE, Trace) /* Remember to update EventNameMAX and EventCodeMAX above! @@ -722,11 +723,6 @@ PARAM(X, 9, W, newDeferredSize) /* new size (deferred) of pool gen */ \ PARAM(X, 10, W, oldDeferredSize) /* old size (deferred) of pool gen */ -#define EVENT_TraceCondemnZones_PARAMS(PARAM, X) \ - PARAM(X, 0, P, trace) /* the trace */ \ - PARAM(X, 1, W, condemnedSet) /* the condemned zoneSet */ \ - PARAM(X, 2, W, white) /* the trace's white zoneSet */ - #define EVENT_ArenaGenZoneAdd_PARAMS(PARAM, X) \ PARAM(X, 0, P, arena) /* the arena */ \ PARAM(X, 1, P, gendesc) /* the generation description */ \ @@ -740,6 +736,14 @@ PARAM(X, 0, P, arena) /* the arena */ \ PARAM(X, 1, D, pauseTime) /* the new maximum pause time, in seconds */ +#define EVENT_TraceEndGen_PARAMS(PARAM, X) \ + PARAM(X, 0, P, trace) /* the trace */ \ + PARAM(X, 1, P, gen) /* the generation */ \ + PARAM(X, 2, W, condemned) /* bytes condemned in generation */ \ + PARAM(X, 3, W, forwarded) /* bytes forwarded from generation */ \ + PARAM(X, 4, W, preservedInPlace) /* bytes preserved in generation */ \ + PARAM(X, 5, D, mortality) /* updated mortality */ + #endif /* eventdef_h */ diff --git a/mps/code/locus.c b/mps/code/locus.c index 0d87366706a..e2624644474 100644 --- a/mps/code/locus.c +++ b/mps/code/locus.c @@ -123,8 +123,8 @@ static Bool GenParamCheck(GenParamStruct *params) { CHECKL(params != NULL); CHECKL(params->capacity > 0); - CHECKL(params->mortality > 0.0); - CHECKL(params->mortality < 1.0); + CHECKL(params->mortality >= 0.0); + CHECKL(params->mortality <= 1.0); return TRUE; } @@ -174,6 +174,79 @@ Size GenDescNewSize(GenDesc gen) } +/* genDescTraceStart -- notify generation of start of a trace */ + +static void genDescStartTrace(GenDesc gen, Trace trace) +{ + GenTraceStats stats; + + AVERT(GenDesc, gen); + AVERT(Trace, trace); + + stats = &gen->trace[trace->ti]; + stats->condemned = 0; + stats->forwarded = 0; + stats->preservedInPlace = 0; +} + + +/* genDescEndTrace -- notify generation of end of a trace */ + +static void genDescEndTrace(GenDesc gen, Trace trace) +{ + GenTraceStats stats; + Size survived; + + AVERT(GenDesc, gen); + AVERT(Trace, trace); + + stats = &gen->trace[trace->ti]; + survived = stats->forwarded + stats->preservedInPlace; + AVER(survived <= stats->condemned); + + if (stats->condemned > 0) { + double mortality = 1.0 - survived / (double)stats->condemned; + double alpha = LocusMortalityALPHA; + gen->mortality = gen->mortality * (1 - alpha) + mortality * alpha; + EVENT6(TraceEndGen, trace, gen, stats->condemned, stats->forwarded, + stats->preservedInPlace, gen->mortality); + } +} + + +/* GenDescCondemned -- memory in a generation was condemned for a trace */ + +void GenDescCondemned(GenDesc gen, Trace trace, Size size) +{ + GenTraceStats stats; + + AVERT(GenDesc, gen); + AVERT(Trace, trace); + + stats = &gen->trace[trace->ti]; + stats->condemned += size; + trace->condemned += size; +} + + +/* GenDescSurvived -- memory in a generation survived a trace */ + +void GenDescSurvived(GenDesc gen, Trace trace, Size forwarded, + Size preservedInPlace) +{ + GenTraceStats stats; + + AVERT(GenDesc, gen); + AVERT(Trace, trace); + + stats = &gen->trace[trace->ti]; + stats->forwarded += forwarded; + stats->preservedInPlace += preservedInPlace; + trace->forwardedSize += forwarded; + trace->preservedInPlaceSize += preservedInPlace; +} + + /* GenDescTotalSize -- return total size of generation */ Size GenDescTotalSize(GenDesc gen) @@ -196,6 +269,7 @@ Size GenDescTotalSize(GenDesc gen) Res GenDescDescribe(GenDesc gen, mps_lib_FILE *stream, Count depth) { + Index i; Res res; Ring node, nextNode; @@ -213,6 +287,18 @@ Res GenDescDescribe(GenDesc gen, mps_lib_FILE *stream, Count depth) if (res != ResOK) return res; + for (i = 0; i < NELEMS(gen->trace); ++i) { + GenTraceStats stats = &gen->trace[i]; + res = WriteF(stream, depth + 2, + "trace $W {\n", (WriteFW)i, + " condemned $W\n", (WriteFW)stats->condemned, + " forwarded $W\n", (WriteFW)stats->forwarded, + " preservedInPlace $W\n", (WriteFW)stats->preservedInPlace, + "}\n", NULL); + if (res != ResOK) + return res; + } + RING_FOR(node, &gen->locusRing, nextNode) { PoolGen pgen = RING_ELT(PoolGen, genRing, node); res = PoolGenDescribe(pgen, stream, depth + 2); @@ -376,25 +462,35 @@ double ChainDeferral(Chain chain) } -/* ChainStartGC -- called to notify start of GC for this chain */ +/* ChainStartTrace -- called to notify start of GC for this chain */ -void ChainStartGC(Chain chain, Trace trace) +void ChainStartTrace(Chain chain, Trace trace) { + Index i; + AVERT(Chain, chain); AVERT(Trace, trace); chain->activeTraces = TraceSetAdd(chain->activeTraces, trace); + + for (i = 0; i < chain->genCount; ++i) + genDescStartTrace(&chain->gens[i], trace); } -/* ChainEndGC -- called to notify end of GC for this chain */ +/* ChainEndTrace -- called to notify end of GC for this chain */ -void ChainEndGC(Chain chain, Trace trace) +void ChainEndTrace(Chain chain, Trace trace) { + Index i; + AVERT(Chain, chain); AVERT(Trace, trace); chain->activeTraces = TraceSetDel(chain->activeTraces, trace); + + for (i = 0; i < chain->genCount; ++i) + genDescEndTrace(&chain->gens[i], trace); } @@ -617,9 +713,10 @@ void PoolGenAccountForEmpty(PoolGen pgen, Size unused, Bool deferred) /* PoolGenAccountForAge -- accounting for condemning * - * Call this when memory is condemned via PoolWhiten. The size - * parameter should be the amount of memory that is being condemned - * for the first time. The deferred flag is as for PoolGenAccountForFill. + * Call this when memory is condemned via PoolWhiten, or when + * artificially ageing memory in PoolGenFree. The size parameter + * should be the amount of memory that is being condemned for the + * first time. The deferred flag is as for PoolGenAccountForFill. * * See */ @@ -627,7 +724,8 @@ void PoolGenAccountForEmpty(PoolGen pgen, Size unused, Bool deferred) void PoolGenAccountForAge(PoolGen pgen, Size size, Bool deferred) { AVERT(PoolGen, pgen); - + AVERT(Bool, deferred); + if (deferred) { AVER(pgen->newDeferredSize >= size); pgen->newDeferredSize -= size; diff --git a/mps/code/locus.h b/mps/code/locus.h index ef0db3a0d6a..f463624552a 100644 --- a/mps/code/locus.h +++ b/mps/code/locus.h @@ -17,11 +17,22 @@ typedef struct GenParamStruct *GenParam; typedef struct GenParamStruct { - Size capacity; /* capacity in kB */ - double mortality; + Size capacity; /* capacity in kB */ + double mortality; /* predicted mortality */ } GenParamStruct; +/* GenTraceStats -- per-generation per-trace statistics */ + +typedef struct GenTraceStatsStruct *GenTraceStats; + +typedef struct GenTraceStatsStruct { + Size condemned; /* size of objects condemned by the trace */ + Size forwarded; /* size of objects that were forwarded by the trace */ + Size preservedInPlace; /* size of objects preserved in place by the trace */ +} GenTraceStatsStruct; + + /* GenDesc -- descriptor of a generation in a chain */ typedef struct GenDescStruct *GenDesc; @@ -30,11 +41,12 @@ typedef struct GenDescStruct *GenDesc; typedef struct GenDescStruct { Sig sig; - ZoneSet zones; /* zoneset for this generation */ - Size capacity; /* capacity in kB */ - double mortality; + ZoneSet zones; /* zoneset for this generation */ + Size capacity; /* capacity in kB */ + double mortality; /* predicted mortality */ RingStruct locusRing; /* Ring of all PoolGen's in this GenDesc (locus) */ RingStruct segRing; /* Ring of GCSegs in this generation */ + GenTraceStatsStruct trace[TraceLIMIT]; } GenDescStruct; @@ -79,6 +91,8 @@ typedef struct mps_chain_s { extern Bool GenDescCheck(GenDesc gen); extern Size GenDescNewSize(GenDesc gen); extern Size GenDescTotalSize(GenDesc gen); +extern void GenDescCondemned(GenDesc gen, Trace trace, Size size); +extern void GenDescSurvived(GenDesc gen, Trace trace, Size forwarded, Size preservedInPlace); extern Res GenDescDescribe(GenDesc gen, mps_lib_FILE *stream, Count depth); extern Res ChainCreate(Chain *chainReturn, Arena arena, size_t genCount, @@ -87,8 +101,8 @@ extern void ChainDestroy(Chain chain); extern Bool ChainCheck(Chain chain); extern double ChainDeferral(Chain chain); -extern void ChainStartGC(Chain chain, Trace trace); -extern void ChainEndGC(Chain chain, Trace trace); +extern void ChainStartTrace(Chain chain, Trace trace); +extern void ChainEndTrace(Chain chain, Trace trace); extern size_t ChainGens(Chain chain); extern GenDesc ChainGen(Chain chain, Index gen); extern Res ChainDescribe(Chain chain, mps_lib_FILE *stream, Count depth); diff --git a/mps/code/mpmst.h b/mps/code/mpmst.h index 34bc8fc9ec6..d49f5e2ca73 100644 --- a/mps/code/mpmst.h +++ b/mps/code/mpmst.h @@ -440,9 +440,7 @@ typedef struct ScanStateStruct { STATISTIC_DECL(Count nailCount); /* segments nailed by ambig refs */ STATISTIC_DECL(Count snapCount); /* refs snapped to forwarded objs */ STATISTIC_DECL(Count forwardedCount); /* objects preserved by moving */ - Size forwardedSize; /* bytes preserved by moving */ STATISTIC_DECL(Count preservedInPlaceCount); /* objects preserved in place */ - Size preservedInPlaceSize; /* bytes preserved in place */ STATISTIC_DECL(Size copiedSize); /* bytes copied */ STATISTIC_DECL(Size scannedSize); /* bytes scanned */ } ScanStateStruct; diff --git a/mps/code/policy.c b/mps/code/policy.c index 78b7a1d73ca..cfa73e7b140 100644 --- a/mps/code/policy.c +++ b/mps/code/policy.c @@ -326,13 +326,13 @@ Bool PolicyStartTrace(Trace *traceReturn, Arena arena) res = TraceCreate(&trace, arena, TraceStartWhyCHAIN_GEN0CAP); AVER(res == ResOK); + trace->chain = firstChain; + ChainStartTrace(firstChain, trace); res = policyCondemnChain(&mortality, firstChain, trace); if (res != ResOK) /* should try some other trace, really @@@@ */ goto failCondemn; if (TraceIsEmpty(trace)) goto nothingCondemned; - trace->chain = firstChain; - ChainStartGC(firstChain, trace); res = TraceStart(trace, mortality, trace->condemned * TraceWorkFactor); /* We don't expect normal GC traces to fail to start. */ AVER(res == ResOK); diff --git a/mps/code/poolamc.c b/mps/code/poolamc.c index 0d4228c7093..5c28396b683 100644 --- a/mps/code/poolamc.c +++ b/mps/code/poolamc.c @@ -93,6 +93,7 @@ typedef struct amcSegStruct { GCSegStruct gcSegStruct; /* superclass fields must come first */ amcGen gen; /* generation this segment belongs to */ Nailboard board; /* nailboard for this segment or NULL if none */ + Size forwarded[TraceLIMIT]; /* size of objects forwarded for each trace */ BOOLFIELD(old); /* .seg.old */ BOOLFIELD(deferred); /* .seg.deferred */ Sig sig; /* */ @@ -1207,10 +1208,6 @@ static Res AMCWhiten(Pool pool, Trace trace, Seg seg) } } - SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); - condemned += SegSize(seg); - trace->condemned += condemned; - amc = PoolAMC(pool); AVERT(AMC, amc); @@ -1221,6 +1218,10 @@ static Res AMCWhiten(Pool pool, Trace trace, Seg seg) amcseg->old = TRUE; } + amcseg->forwarded[trace->ti] = 0; + SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); + GenDescCondemned(gen->pgen.gen, trace, condemned + SegSize(seg)); + /* Ensure we are forwarding into the right generation. */ /* see */ @@ -1635,7 +1636,6 @@ static Res AMCFix(Pool pool, ScanState ss, Seg seg, Ref *refIO) length = AddrOffset(ref, clientQ); /* .exposed.seg */ STATISTIC_STAT(++ss->forwardedCount); - ss->forwardedSize += length; do { res = BUFFER_RESERVE(&newBase, buffer, length); if (res != ResOK) @@ -1695,6 +1695,7 @@ static void amcReclaimNailed(Pool pool, Trace trace, Seg seg) Count preservedInPlaceCount = (Count)0; Size preservedInPlaceSize = (Size)0; AMC amc; + PoolGen pgen; Size headerSize; Addr padBase; /* base of next padding object */ Size padLength; /* length of next padding object */ @@ -1773,19 +1774,19 @@ static void amcReclaimNailed(Pool pool, Trace trace, Seg seg) AVER(bytesReclaimed <= SegSize(seg)); trace->reclaimSize += bytesReclaimed; trace->preservedInPlaceCount += preservedInPlaceCount; - trace->preservedInPlaceSize += preservedInPlaceSize; + pgen = &amcSegGen(seg)->pgen; + GenDescSurvived(pgen->gen, trace, Seg2amcSeg(seg)->forwarded[trace->ti], + preservedInPlaceSize); /* Free the seg if we can; fixes .nailboard.limitations.middle. */ if(preservedInPlaceCount == 0 && (SegBuffer(seg) == NULL) && (SegNailed(seg) == TraceSetEMPTY)) { - amcGen gen = amcSegGen(seg); - /* We may not free a buffered seg. */ AVER(SegBuffer(seg) == NULL); - PoolGenFree(&gen->pgen, seg, 0, SegSize(seg), 0, Seg2amcSeg(seg)->deferred); + PoolGenFree(pgen, seg, 0, SegSize(seg), 0, Seg2amcSeg(seg)->deferred); } } @@ -1832,6 +1833,7 @@ static void AMCReclaim(Pool pool, Trace trace, Seg seg) trace->reclaimSize += SegSize(seg); + GenDescSurvived(gen->pgen.gen, trace, Seg2amcSeg(seg)->forwarded[trace->ti], 0); PoolGenFree(&gen->pgen, seg, 0, SegSize(seg), 0, Seg2amcSeg(seg)->deferred); } diff --git a/mps/code/poolams.c b/mps/code/poolams.c index 3ca99d8ec8b..a7e80fe2b20 100644 --- a/mps/code/poolams.c +++ b/mps/code/poolams.c @@ -1078,7 +1078,7 @@ static Res AMSWhiten(Pool pool, Trace trace, Seg seg) AMS ams; AMSSeg amsseg; Buffer buffer; /* the seg's buffer, if it has one */ - Count uncondemned; + Count uncondemnedGrains, condemnedGrains; AVERT(Pool, pool); ams = PoolAMS(pool); @@ -1128,21 +1128,23 @@ static Res AMSWhiten(Pool pool, Trace trace, Seg seg) AMS_RANGE_BLACKEN(seg, scanLimitIndex, limitIndex); amsRangeWhiten(seg, limitIndex, amsseg->grains); /* We didn't condemn the buffer, subtract it from the count. */ - uncondemned = limitIndex - scanLimitIndex; + uncondemnedGrains = limitIndex - scanLimitIndex; } else { /* condemn whole seg */ amsRangeWhiten(seg, 0, amsseg->grains); - uncondemned = (Count)0; + uncondemnedGrains = (Count)0; } /* The unused part of the buffer remains new: the rest becomes old. */ - PoolGenAccountForAge(&ams->pgen, AMSGrainsSize(ams, amsseg->newGrains - uncondemned), FALSE); - amsseg->oldGrains += amsseg->newGrains - uncondemned; - amsseg->newGrains = uncondemned; + condemnedGrains = amsseg->newGrains - uncondemnedGrains; + PoolGenAccountForAge(&ams->pgen, AMSGrainsSize(ams, condemnedGrains), FALSE); + amsseg->oldGrains += condemnedGrains; + amsseg->newGrains = uncondemnedGrains; amsseg->marksChanged = FALSE; /* */ amsseg->ambiguousFixes = FALSE; if (amsseg->oldGrains > 0) { - trace->condemned += AMSGrainsSize(ams, amsseg->oldGrains); + GenDescCondemned(ams->pgen.gen, trace, + AMSGrainsSize(ams, amsseg->oldGrains)); SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); } else { amsseg->colourTablesInUse = FALSE; @@ -1550,6 +1552,7 @@ static void AMSReclaim(Pool pool, Trace trace, Seg seg) AMS ams; AMSSeg amsseg; Count nowFree, grains, reclaimedGrains; + Size preservedInPlaceSize; PoolDebugMixin debug; AVERT(Pool, pool); @@ -1601,7 +1604,8 @@ static void AMSReclaim(Pool pool, Trace trace, Seg seg) PoolGenAccountForReclaim(&ams->pgen, AMSGrainsSize(ams, reclaimedGrains), FALSE); trace->reclaimSize += AMSGrainsSize(ams, reclaimedGrains); /* preservedInPlaceCount is updated on fix */ - trace->preservedInPlaceSize += AMSGrainsSize(ams, amsseg->oldGrains); + preservedInPlaceSize = AMSGrainsSize(ams, amsseg->oldGrains); + GenDescSurvived(ams->pgen.gen, trace, 0, preservedInPlaceSize); /* Ensure consistency of segment even if are just about to free it */ amsseg->colourTablesInUse = FALSE; diff --git a/mps/code/poolawl.c b/mps/code/poolawl.c index eb61281526c..f4dba2595ea 100644 --- a/mps/code/poolawl.c +++ b/mps/code/poolawl.c @@ -753,7 +753,7 @@ static Res AWLWhiten(Pool pool, Trace trace, Seg seg) AWL awl; AWLSeg awlseg; Buffer buffer; - Count uncondemned; + Count uncondemnedGrains, condemnedGrains; /* All parameters checked by generic PoolWhiten. */ @@ -769,13 +769,13 @@ static Res AWLWhiten(Pool pool, Trace trace, Seg seg) if(buffer == NULL) { awlRangeWhiten(awlseg, 0, awlseg->grains); - uncondemned = (Count)0; + uncondemnedGrains = (Count)0; } else { /* Whiten everything except the buffer. */ Addr base = SegBase(seg); Index scanLimitIndex = awlIndexOfAddr(base, awl, BufferScanLimit(buffer)); Index limitIndex = awlIndexOfAddr(base, awl, BufferLimit(buffer)); - uncondemned = limitIndex - scanLimitIndex; + uncondemnedGrains = limitIndex - scanLimitIndex; awlRangeWhiten(awlseg, 0, scanLimitIndex); awlRangeWhiten(awlseg, limitIndex, awlseg->grains); @@ -788,12 +788,14 @@ static Res AWLWhiten(Pool pool, Trace trace, Seg seg) } } - PoolGenAccountForAge(&awl->pgen, AWLGrainsSize(awl, awlseg->newGrains - uncondemned), FALSE); - awlseg->oldGrains += awlseg->newGrains - uncondemned; - awlseg->newGrains = uncondemned; + condemnedGrains = awlseg->newGrains - uncondemnedGrains; + PoolGenAccountForAge(&awl->pgen, AWLGrainsSize(awl, condemnedGrains), FALSE); + awlseg->oldGrains += condemnedGrains; + awlseg->newGrains = uncondemnedGrains; if (awlseg->oldGrains > 0) { - trace->condemned += AWLGrainsSize(awl, awlseg->oldGrains); + GenDescCondemned(awl->pgen.gen, trace, + AWLGrainsSize(awl, awlseg->oldGrains)); SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); } @@ -1172,7 +1174,7 @@ static void AWLReclaim(Pool pool, Trace trace, Seg seg) trace->reclaimSize += AWLGrainsSize(awl, reclaimedGrains); trace->preservedInPlaceCount += preservedInPlaceCount; - trace->preservedInPlaceSize += preservedInPlaceSize; + GenDescSurvived(awl->pgen.gen, trace, 0, preservedInPlaceSize); SegSetWhite(seg, TraceSetDel(SegWhite(seg), trace)); if (awlseg->freeGrains == awlseg->grains && buffer == NULL) diff --git a/mps/code/poollo.c b/mps/code/poollo.c index 23c73e38cfa..ea6bfe65a5b 100644 --- a/mps/code/poollo.c +++ b/mps/code/poollo.c @@ -381,8 +381,7 @@ static void loSegReclaim(LOSeg loseg, Trace trace) trace->reclaimSize += LOGrainsSize(lo, reclaimedGrains); trace->preservedInPlaceCount += preservedInPlaceCount; - trace->preservedInPlaceSize += preservedInPlaceSize; - + GenDescSurvived(lo->pgen.gen, trace, 0, preservedInPlaceSize); SegSetWhite(seg, TraceSetDel(SegWhite(seg), trace)); if (!marked) @@ -671,7 +670,7 @@ static Res LOWhiten(Pool pool, Trace trace, Seg seg) LO lo; LOSeg loseg; Buffer buffer; - Count grains, uncondemned; + Count grains, uncondemnedGrains, condemnedGrains; AVERT(Pool, pool); lo = PoolPoolLO(pool); @@ -691,21 +690,25 @@ static Res LOWhiten(Pool pool, Trace trace, Seg seg) Addr base = SegBase(seg); Index scanLimitIndex = loIndexOfAddr(base, lo, BufferScanLimit(buffer)); Index limitIndex = loIndexOfAddr(base, lo, BufferLimit(buffer)); - uncondemned = limitIndex - scanLimitIndex; + uncondemnedGrains = limitIndex - scanLimitIndex; if (0 < scanLimitIndex) BTCopyInvertRange(loseg->alloc, loseg->mark, 0, scanLimitIndex); if (limitIndex < grains) BTCopyInvertRange(loseg->alloc, loseg->mark, limitIndex, grains); } else { - uncondemned = (Count)0; + uncondemnedGrains = (Count)0; BTCopyInvertRange(loseg->alloc, loseg->mark, 0, grains); } - PoolGenAccountForAge(&lo->pgen, LOGrainsSize(lo, loseg->newGrains - uncondemned), FALSE); - loseg->oldGrains += loseg->newGrains - uncondemned; - loseg->newGrains = uncondemned; - trace->condemned += LOGrainsSize(lo, loseg->oldGrains); - SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); + condemnedGrains = loseg->newGrains - uncondemnedGrains; + PoolGenAccountForAge(&lo->pgen, LOGrainsSize(lo, condemnedGrains), FALSE); + loseg->oldGrains += condemnedGrains; + loseg->newGrains = uncondemnedGrains; + + if (loseg->oldGrains > 0) { + GenDescCondemned(lo->pgen.gen, trace, LOGrainsSize(lo, loseg->oldGrains)); + SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace)); + } return ResOK; } diff --git a/mps/code/trace.c b/mps/code/trace.c index c817538f73a..064cc46382f 100644 --- a/mps/code/trace.c +++ b/mps/code/trace.c @@ -105,9 +105,7 @@ void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, STATISTIC(ss->nailCount = (Count)0); STATISTIC(ss->snapCount = (Count)0); STATISTIC(ss->forwardedCount = (Count)0); - ss->forwardedSize = (Size)0; /* see .message.data */ STATISTIC(ss->preservedInPlaceCount = (Count)0); - ss->preservedInPlaceSize = (Size)0; /* see .message.data */ STATISTIC(ss->copiedSize = (Size)0); ss->scannedSize = (Size)0; /* see .work */ ss->sig = ScanStateSig; @@ -296,11 +294,7 @@ static void traceUpdateCounts(Trace trace, ScanState ss, STATISTIC(trace->nailCount += ss->nailCount); STATISTIC(trace->snapCount += ss->snapCount); STATISTIC(trace->forwardedCount += ss->forwardedCount); - trace->forwardedSize += ss->forwardedSize; /* see .message.data */ STATISTIC(trace->preservedInPlaceCount += ss->preservedInPlaceCount); - trace->preservedInPlaceSize += ss->preservedInPlaceSize; - - return; } @@ -741,6 +735,33 @@ found: } +/* traceDestroyCommon -- common functionality for TraceDestroy* */ + +static void traceDestroyCommon(Trace trace) +{ + Ring chainNode, nextChainNode; + + if (trace->chain != NULL) { + ChainEndTrace(trace->chain, trace); + } else { + /* Notify all the chains. */ + RING_FOR(chainNode, &trace->arena->chainRing, nextChainNode) { + Chain chain = RING_ELT(Chain, chainRing, chainNode); + ChainEndTrace(chain, trace); + } + } + + EVENT1(TraceDestroy, trace); + + trace->sig = SigInvalid; + trace->arena->busyTraces = TraceSetDel(trace->arena->busyTraces, trace); + trace->arena->flippedTraces = TraceSetDel(trace->arena->flippedTraces, trace); + + /* Hopefully the trace reclaimed some memory, so clear any emergency. */ + ArenaSetEmergency(trace->arena, FALSE); +} + + /* TraceDestroyInit -- destroy a trace object in state INIT */ void TraceDestroyInit(Trace trace) @@ -748,14 +769,9 @@ void TraceDestroyInit(Trace trace) AVERT(Trace, trace); AVER(trace->state == TraceINIT); AVER(trace->condemned == 0); + AVER(!TraceSetIsMember(trace->arena->flippedTraces, trace)); - EVENT1(TraceDestroy, trace); - - trace->sig = SigInvalid; - trace->arena->busyTraces = TraceSetDel(trace->arena->busyTraces, trace); - - /* Clear the emergency flag so the next trace starts normally. */ - ArenaSetEmergency(trace->arena, FALSE); + traceDestroyCommon(trace); } @@ -773,19 +789,6 @@ void TraceDestroyFinished(Trace trace) AVERT(Trace, trace); AVER(trace->state == TraceFINISHED); - if(trace->chain == NULL) { - Ring chainNode, nextChainNode; - - /* Notify all the chains. */ - RING_FOR(chainNode, &trace->arena->chainRing, nextChainNode) { - Chain chain = RING_ELT(Chain, chainRing, chainNode); - - ChainEndGC(chain, trace); - } - } else { - ChainEndGC(trace->chain, trace); - } - STATISTIC_STAT(EVENT13 (TraceStatScan, trace, trace->rootScanCount, trace->rootScanSize, @@ -808,14 +811,7 @@ void TraceDestroyFinished(Trace trace) (TraceStatReclaim, trace, trace->reclaimCount, trace->reclaimSize)); - EVENT1(TraceDestroy, trace); - - trace->sig = SigInvalid; - trace->arena->busyTraces = TraceSetDel(trace->arena->busyTraces, trace); - trace->arena->flippedTraces = TraceSetDel(trace->arena->flippedTraces, trace); - - /* Hopefully the trace reclaimed some memory, so clear any emergency. */ - ArenaSetEmergency(trace->arena, FALSE); + traceDestroyCommon(trace); } @@ -1490,7 +1486,7 @@ static Res traceCondemnAll(Trace trace) { Res res; Arena arena; - Ring poolNode, nextPoolNode, chainNode, nextChainNode; + Ring poolNode, nextPoolNode; arena = trace->arena; AVERT(Arena, arena); @@ -1522,12 +1518,6 @@ static Res traceCondemnAll(Trace trace) if (TraceIsEmpty(trace)) return ResFAIL; - /* Notify all the chains. */ - RING_FOR(chainNode, &arena->chainRing, nextChainNode) { - Chain chain = RING_ELT(Chain, chainRing, chainNode); - - ChainStartGC(chain, trace); - } return ResOK; failBegin: @@ -1749,12 +1739,20 @@ Res TraceStartCollectAll(Trace *traceReturn, Arena arena, int why) Trace trace = NULL; Res res; double finishingTime; + Ring chainNode, nextChainNode; AVERT(Arena, arena); AVER(arena->busyTraces == TraceSetEMPTY); res = TraceCreate(&trace, arena, why); AVER(res == ResOK); /* succeeds because no other trace is busy */ + + /* Notify all the chains. */ + RING_FOR(chainNode, &arena->chainRing, nextChainNode) { + Chain chain = RING_ELT(Chain, chainRing, chainNode); + ChainStartTrace(chain, trace); + } + res = traceCondemnAll(trace); if(res != ResOK) /* should try some other trace, really @@@@ */ goto failCondemn; diff --git a/mps/manual/source/guide/perf.rst b/mps/manual/source/guide/perf.rst index 805d706f169..adea19cc761 100644 --- a/mps/manual/source/guide/perf.rst +++ b/mps/manual/source/guide/perf.rst @@ -34,45 +34,32 @@ short-lived objects.) First, the effect of varying the capacity of a chain with a single generation. -======== ========= ========================= -Capacity Mortality Execution time (user+sys) -======== ========= ========================= -100 0.80 362.6 -200 0.80 354.9 -400 0.80 349.7 -800 0.80 314.4 -1600 0.80 215.7 -3200 0.80 94.0 -6400 0.80 53.5 -12800 0.80 79.6 -25600 0.80 77.6 -======== ========= ========================= +======== ========================= +Capacity Execution time (user+sys) +======== ========================= +100 362.6 +200 354.9 +400 349.7 +800 314.4 +1600 215.7 +3200 94.0 +6400 53.5 +12800 79.6 +25600 77.6 +======== ========================= -Second, the effect of varying the mortality of a chain with a single -generation. - -======== ========= ========================= -Capacity Mortality Execution time (user+sys) -======== ========= ========================= -6400 0.20 55.4 -6400 0.40 54.0 -6400 0.60 54.0 -6400 0.80 53.5 -6400 0.99 54.8 -======== ========= ========================= - -Third, the effect of varying the number of generations (all +Second, the effect of varying the number of generations (all generations being identical). -=========== ======== ========= ========================= -Generations Capacity Mortality Execution time (user+sys) -=========== ======== ========= ========================= -1 6400 0.80 53.5 -2 6400 0.80 42.4 -3 6400 0.80 42.1 -4 6400 0.80 42.2 -5 6400 0.80 42.2 -=========== ======== ========= ========================= +=========== ======== ========================= +Generations Capacity Execution time (user+sys) +=========== ======== ========================= +1 6400 53.5 +2 6400 42.4 +3 6400 42.1 +4 6400 42.2 +5 6400 42.2 +=========== ======== ========================= These tables suggest that: @@ -80,10 +67,6 @@ These tables suggest that: sizes right is dramatic: much bigger than the small improvements to gained from other techniques. -#. The predicted mortality doesn't make much difference to the overall - execution time (it does affect the distribution of pause times, - however: see :ref:`topic-collection-schedule`.) - #. You can make generations too big as well as too small. #. There are rapidly diminishing returns to be gained from adding @@ -97,7 +80,7 @@ These tables suggest that: The table below shows the effect of varying the initial allocation of address space to the arena (using three generations each with capacity -6400 kB, mortality 0.80). +6400 kB). ============= ========== =========== ========================= Address space Extensions Collections Execution time (user+sys) diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst index 10fda30689c..2600eb217be 100644 --- a/mps/manual/source/release.rst +++ b/mps/manual/source/release.rst @@ -4,6 +4,22 @@ Release notes ============= +.. _release-notes-1.116: + +Release 1.116.0 +--------------- + + +New features +............ + +#. The MPS now measures the mortality of a :term:`generation` each + time it is collected, and maintains a moving average. This means + that it is no longer important to provide an accurate estimate of + the mortality when creating a :term:`generation chain` by calling + :c:func:`mps_chain_create`.. + + .. _release-notes-1.115: Release 1.115.0 diff --git a/mps/manual/source/topic/arena.rst b/mps/manual/source/topic/arena.rst index d538402cba9..a0a64617e5f 100644 --- a/mps/manual/source/topic/arena.rst +++ b/mps/manual/source/topic/arena.rst @@ -461,8 +461,8 @@ Arena properties When the pause time is short, the MPS needs to take more slices of time in order to make :term:`garbage collection` progress, and make more use of :term:`barriers (1)` to support - :term:`incremental collection`. This increases time overheads, - and especially operating system overheads. + :term:`incremental garbage collection`. This increases time + overheads, and especially operating system overheads. The pause time may be set to zero, in which case the MPS returns as soon as it can, without regard for overall efficiency. This @@ -476,7 +476,7 @@ Arena properties The pause time may be set to infinity, in which case the MPS completes all outstanding :term:`garbage collection` work before returning from an operation. The consequence is that the MPS will - be able to save on the overheads due to :term:`incremental + be able to save on the overheads due to :term:`incremental garbage collection`, leading to lower total time spent in collection. This value is suitable for non-interactive applications where total time is important. diff --git a/mps/manual/source/topic/collection.rst b/mps/manual/source/topic/collection.rst index d911172f413..e0c7563b471 100644 --- a/mps/manual/source/topic/collection.rst +++ b/mps/manual/source/topic/collection.rst @@ -55,8 +55,9 @@ you can choose which chain it should use by passing the Create a generation chain by preparing an array of :c:type:`mps_gen_param_s` structures giving the *capacity* (in -kilobytes) and *predicted mortality* (between 0 and 1) of each -generation, and passing them to :c:func:`mps_chain_create`. +kilobytes) and *initial predicted mortality* (between 0 and 1 +inclusive) of each generation, and passing them to +:c:func:`mps_chain_create`. When the *new size* of a generation exceeds its capacity, the MPS will be prepared to start collecting the chain to which the generation @@ -95,17 +96,29 @@ For example:: } mps_gen_param_s; ``mps_capacity`` is the capacity of the generation, in - :term:`kilobytes`. When the size of the generation - exceeds this, the MPS will be prepared to start collecting it. + :term:`kilobytes`. When the size of the generation exceeds this, + the MPS will be prepared to start collecting it. - ``mps_mortality`` is the predicted mortality of the generation: - the proportion (between 0 and 1) of blocks in the generation that - are expected to be :term:`dead` when the generation is collected. + .. note:: - These numbers are hints to the MPS that it may use to make - decisions about when and what to collect: nothing will go wrong - (other than suboptimal performance) if you make poor - choices. See :ref:`topic-collection-schedule`. + The name *capacity* is somewhat misleading. When a generation + reaches its capacity the MPS may not be able to collect it + immediately (for example because some other generation is + being collected), but this does not prevent allocation into + the generation, and so the size of a generation will often + exceed its capacity. + + ``mps_mortality`` is the initial predicted mortality of the + generation: the proportion (between 0 and 1 inclusive) of bytes in + the generation that are expected to be :term:`dead` when the + generation is collected. + + .. note:: + + This value is only used as an initial estimate. The MPS + measures the mortality each time it collects the generation, + and maintains a moving average. So it is not important to + provide an accurate estimate here. .. c:function:: mps_res_t mps_chain_create(mps_chain_t *chain_o, mps_arena_t arena, size_t gen_count, mps_gen_param_s *gen_params) @@ -187,28 +200,6 @@ survive collection get promoted to generation *g*\+1. If the last generation in the chain is collected, the survivors are promoted into an :term:`arena`\-wide "top" generation. -The predicted mortality is used to estimate how long the collection -will take, and this is used in turn to decide how much work the -collector will do each time it has an opportunity to do some work. The constraints here are: - -#. The :term:`client program` might have specified a limit on the - acceptable length of the pause if the work is being done inside - :c:func:`mps_arena_step`. - -#. The collector needs to keep up with the :term:`client program`: - that is, it has to collect garbage at least as fast as the client - is producing it, otherwise the amount of garbage will grow without - bound. - -With perfect prediction, the collector's work should be smoothly -distributed, with a small maximum pause time. Getting the predicted -mortality wrong leads to "lumpy" distribution of collection work with -a longer maximum pause time. If the predicted mortality is too high, -the collector will start out by taking small time slices and then find -that it has to catch up later by taking larger time slices. If the -predicted mortality is too low, the collector will take larger time -slices up front and then find that it is idle later on. - .. index:: single: garbage collection; start message