GC fixes/improvements

- Have a real finalizer stage for destroying objects instead of mixing them
  in to the sweep stage.
- Base GC rate on a running average of the allocation rate instead of basing
  it on the amount of time taken since the last sweep started.
- More GC stats for better tweaking.
This commit is contained in:
Marisa Heit 2022-11-06 11:19:26 -06:00 committed by Christoph Oelckers
parent 7e10138993
commit 7f899bd412
4 changed files with 381 additions and 173 deletions

View file

@ -221,6 +221,11 @@ public:
Counter = 0; Counter = 0;
} }
void ResetAndClock()
{
Counter = -static_cast<int64_t>(rdtsc());
}
void Clock() void Clock()
{ {
int64_t time = rdtsc(); int64_t time = rdtsc();

View file

@ -82,23 +82,58 @@
** infinity, where each step performs a full collection.) You can also ** infinity, where each step performs a full collection.) You can also
** change this value dynamically. ** change this value dynamically.
*/ */
#define DEFAULT_GCMUL 200 // GC runs 'double the speed' of memory allocation #ifndef _DEBUG
#define DEFAULT_GCMUL 600 // GC runs gcmul% the speed of memory allocation
#else
// Higher in debug builds to account for the extra time spent freeing objects
#define DEFAULT_GCMUL 800
#endif
// Minimum step size // Minimum step size
#define GCSTEPSIZE (sizeof(DObject) * 16) #define GCMINSTEPSIZE (sizeof(DObject) * 16)
// Maximum number of elements to sweep in a single step // Sweeps traverse objects in chunks of this size
#define GCSWEEPMAX 40 #define GCSWEEPGRANULARITY 40
// Cost of sweeping one element (the size of a small object divided by // Cost of deleting an object
// some adjust for the sweep speed) #ifndef _DEBUG
#define GCSWEEPCOST (sizeof(DObject) / 4) #define GCDELETECOST 75
#else
// Freeing memory is much more costly in debug builds
#define GCDELETECOST 230
#endif
// Cost of calling of one destructor // Cost of destroying an object
#define GCFINALIZECOST 100 #define GCDESTROYCOST 15
// TYPES ------------------------------------------------------------------- // TYPES -------------------------------------------------------------------
class FAveragizer
{
// Number of allocations to track
static inline constexpr unsigned HistorySize = 512;
size_t History[HistorySize];
size_t TotalAmount;
int TotalCount;
unsigned NewestPos;
public:
FAveragizer();
void AddAlloc(size_t alloc);
size_t GetAverage();
};
struct FStepStats
{
cycle_t Clock[GC::GCS_COUNT];
size_t BytesCovered[GC::GCS_COUNT];
int Count[GC::GCS_COUNT];
void Format(FString &out);
void Reset();
};
// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
@ -114,28 +149,50 @@ static size_t CalcStepSize();
namespace GC namespace GC
{ {
size_t AllocBytes; size_t AllocBytes;
size_t RunningAllocBytes;
size_t RunningDeallocBytes;
size_t Threshold; size_t Threshold;
size_t Estimate; size_t Estimate;
DObject *Gray; DObject *Gray;
DObject *Root; DObject *Root;
DObject *SoftRoots; DObject *SoftRoots;
DObject **SweepPos; DObject **SweepPos;
DObject *ToDestroy;
uint32_t CurrentWhite = OF_White0 | OF_Fixed; uint32_t CurrentWhite = OF_White0 | OF_Fixed;
EGCState State = GCS_Pause; EGCState State = GCS_Pause;
int Pause = DEFAULT_GCPAUSE; int Pause = DEFAULT_GCPAUSE;
int StepMul = DEFAULT_GCMUL; int StepMul = DEFAULT_GCMUL;
int StepCount; FStepStats StepStats;
uint64_t CheckTime; FStepStats PrevStepStats;
bool FinalGC; bool FinalGC;
bool HadToDestroy;
// PRIVATE DATA DEFINITIONS ------------------------------------------------ // PRIVATE DATA DEFINITIONS ------------------------------------------------
static int LastCollectTime; // Time last time collector finished static FAveragizer AllocHistory;// Tracks allocation rate over time
static size_t LastCollectAlloc; // Memory allocation when collector finished static cycle_t GCTime; // Track time spent in GC
static size_t MinStepSize; // Cover at least this much memory per step
// CODE -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
//==========================================================================
//
// CheckGC
//
// Check if it's time to collect, and do a collection step if it is.
// Also does some bookkeeping. Should be called fairly consistantly.
//
//==========================================================================
void CheckGC()
{
AllocHistory.AddAlloc(RunningAllocBytes);
RunningAllocBytes = 0;
if (State > GCS_Pause || AllocBytes >= Threshold)
{
Step();
}
}
//========================================================================== //==========================================================================
// //
// SetThreshold // SetThreshold
@ -146,7 +203,7 @@ static size_t MinStepSize; // Cover at least this much memory per step
void SetThreshold() void SetThreshold()
{ {
Threshold = (Estimate / 100) * Pause; Threshold = (std::min(Estimate, AllocBytes) / 100) * Pause;
} }
//========================================================================== //==========================================================================
@ -170,55 +227,72 @@ size_t PropagateMark()
//========================================================================== //==========================================================================
// //
// SweepList // SweepObjects
// //
// Runs a limited sweep on a list, returning the position in the list just // Runs a limited sweep on the object list, returning the number of bytes
// after the last object swept. // swept.
// //
//========================================================================== //==========================================================================
static DObject **SweepList(DObject **p, size_t count, size_t *finalize_count) static size_t SweepObjects(size_t count)
{ {
DObject *curr; DObject *curr;
int deadmask = OtherWhite(); int deadmask = OtherWhite();
size_t finalized = 0; size_t swept = 0;
while ((curr = *p) != NULL && count-- > 0) while ((curr = *SweepPos) != nullptr && count-- > 0)
{ {
swept += curr->GetClass()->Size;
if ((curr->ObjectFlags ^ OF_WhiteBits) & deadmask) // not dead? if ((curr->ObjectFlags ^ OF_WhiteBits) & deadmask) // not dead?
{ {
assert(!curr->IsDead() || (curr->ObjectFlags & OF_Fixed)); assert(!curr->IsDead() || (curr->ObjectFlags & OF_Fixed));
curr->MakeWhite(); // make it white (for next cycle) curr->MakeWhite(); // make it white (for next cycle)
p = &curr->ObjNext; SweepPos = &curr->ObjNext;
} }
else // must erase 'curr' else
{ {
assert(curr->IsDead()); assert(curr->IsDead());
*p = curr->ObjNext;
if (!(curr->ObjectFlags & OF_EuthanizeMe)) if (!(curr->ObjectFlags & OF_EuthanizeMe))
{ // The object must be destroyed before it can be finalized. { // The object must be destroyed before it can be deleted.
// Note that thinkers must already have been destroyed. If they get here without curr->GCNext = ToDestroy;
// having been destroyed first, it means they somehow became unattached from the ToDestroy = curr;
// thinker lists. If I don't maintain the invariant that all live thinkers must SweepPos = &curr->ObjNext;
// be in a thinker list, then I need to add write barriers for every time a
// thinker pointer is changed. This seems easier and perfectly reasonable, since
// a live thinker that isn't on a thinker list isn't much of a thinker.
// However, this can happen during deletion of the thinker list while cleaning up
// from a savegame error so we can't assume that any thinker that gets here is an error.
curr->Destroy();
} }
else
{ // must erase 'curr'
*SweepPos = curr->ObjNext;
curr->ObjectFlags |= OF_Cleanup; curr->ObjectFlags |= OF_Cleanup;
delete curr; delete curr;
finalized++; swept += GCDELETECOST;
} }
} }
if (finalize_count != NULL) }
return swept;
}
//==========================================================================
//
// DestroyObjects
//
// Destroys up to count objects on a list linked on GCNext, returning the
// size of objects destroyed, for updating the estimate.
//
//==========================================================================
static size_t DestroyObjects(size_t count)
{
DObject *curr;
size_t bytes_destroyed = 0;
while ((curr = ToDestroy) != nullptr && count-- > 0)
{ {
*finalize_count = finalized; assert(!(curr->ObjectFlags & OF_EuthanizeMe));
bytes_destroyed += curr->GetClass()->Size + GCDESTROYCOST;
ToDestroy = curr->GCNext;
curr->GCNext = nullptr;
curr->Destroy();
} }
return p; return bytes_destroyed;
} }
//========================================================================== //==========================================================================
@ -269,20 +343,14 @@ void MarkArray(DObject **obj, size_t count)
// //
// CalcStepSize // CalcStepSize
// //
// Decide how big a step should be based, depending on how long it took to // Decide how big a step should be, based on the current allocation rate.
// allocate up to the threshold from the amount left after the previous
// collection.
// //
//========================================================================== //==========================================================================
static size_t CalcStepSize() static size_t CalcStepSize()
{ {
int time_passed = int(CheckTime - LastCollectTime); size_t avg = AllocHistory.GetAverage();
auto alloc = min(LastCollectAlloc, Estimate); return std::max<size_t>(GCMINSTEPSIZE, avg * StepMul / 100);
size_t bytes_gained = AllocBytes > alloc ? AllocBytes - alloc : 0;
return (StepMul > 0 && time_passed > 0)
? std::max<size_t>(GCSTEPSIZE, bytes_gained / time_passed * StepMul / 100)
: std::numeric_limits<size_t>::max() / 2; // no limit
} }
//========================================================================== //==========================================================================
@ -302,15 +370,18 @@ void AddMarkerFunc(GCMarkerFunc func)
static void MarkRoot() static void MarkRoot()
{ {
Gray = NULL; PrevStepStats = StepStats;
StepStats.Reset();
Gray = nullptr;
for (auto func : markers) func(); for (auto func : markers) func();
// Mark soft roots. // Mark soft roots.
if (SoftRoots != NULL) if (SoftRoots != nullptr)
{ {
DObject **probe = &SoftRoots->ObjNext; DObject **probe = &SoftRoots->ObjNext;
while (*probe != NULL) while (*probe != nullptr)
{ {
DObject *soft = *probe; DObject *soft = *probe;
probe = &soft->ObjNext; probe = &soft->ObjNext;
@ -322,7 +393,6 @@ static void MarkRoot()
} }
// Time to propagate the marks. // Time to propagate the marks.
State = GCS_Propagate; State = GCS_Propagate;
StepCount = 0;
} }
//========================================================================== //==========================================================================
@ -341,10 +411,21 @@ static void Atomic()
SweepPos = &Root; SweepPos = &Root;
State = GCS_Sweep; State = GCS_Sweep;
Estimate = AllocBytes; Estimate = AllocBytes;
}
// Now that we are about to start a sweep, establish a baseline minimum //==========================================================================
// step size for how much memory we want to sweep each CheckGC(). //
MinStepSize = CalcStepSize(); // SweepDone
//
// Sets up the Destroy phase, if there are any dead objects that haven't
// been destroyed yet, or skips to the Done state.
//
//==========================================================================
static void SweepDone()
{
HadToDestroy = ToDestroy != nullptr;
State = HadToDestroy ? GCS_Destroy : GCS_Done;
} }
//========================================================================== //==========================================================================
@ -364,7 +445,7 @@ static size_t SingleStep()
return 0; return 0;
case GCS_Propagate: case GCS_Propagate:
if (Gray != NULL) if (Gray != nullptr)
{ {
return PropagateMark(); return PropagateMark();
} }
@ -375,22 +456,30 @@ static size_t SingleStep()
} }
case GCS_Sweep: { case GCS_Sweep: {
size_t old = AllocBytes; RunningDeallocBytes = 0;
size_t finalize_count; size_t swept = SweepObjects(GCSWEEPGRANULARITY);
SweepPos = SweepList(SweepPos, GCSWEEPMAX, &finalize_count); Estimate -= RunningDeallocBytes;
if (*SweepPos == NULL) if (*SweepPos == nullptr)
{ // Nothing more to sweep? { // Nothing more to sweep?
State = GCS_Finalize; SweepDone();
} }
//assert(old >= AllocBytes); return swept;
Estimate -= max<size_t>(0, old - AllocBytes);
return (GCSWEEPMAX - finalize_count) * GCSWEEPCOST + finalize_count * GCFINALIZECOST;
} }
case GCS_Finalize: case GCS_Destroy: {
size_t destroy_size;
destroy_size = DestroyObjects(GCSWEEPGRANULARITY);
Estimate -= destroy_size;
if (ToDestroy == nullptr)
{ // Nothing more to destroy?
State = GCS_Done;
}
return destroy_size;
}
case GCS_Done:
State = GCS_Pause; // end collection State = GCS_Pause; // end collection
LastCollectAlloc = AllocBytes; SetThreshold();
LastCollectTime = (int)CheckTime;
return 0; return 0;
default: default:
@ -403,21 +492,27 @@ static size_t SingleStep()
// //
// Step // Step
// //
// Performs enough single steps to cover GCSTEPSIZE * StepMul% bytes of // Performs enough single steps to cover <StepSize> bytes of memory.
// memory. // Some of those bytes might be "fake" to account for the cost of freeing
// or destroying object.
// //
//========================================================================== //==========================================================================
void Step() void Step()
{ {
// We recalculate a step size in case the rate of allocation went up GCTime.ResetAndClock();
// since we started sweeping because we don't want to fall behind.
// However, we also don't want to go slower than what was decided upon auto enter_state = State;
// when the sweep began if the rate of allocation has slowed. StepStats.Count[enter_state]++;
size_t lim = max(CalcStepSize(), MinStepSize); StepStats.Clock[enter_state].Clock();
size_t did = 0;
size_t lim = CalcStepSize();
do do
{ {
size_t done = SingleStep(); size_t done = SingleStep();
did += done;
if (done < lim) if (done < lim)
{ {
lim -= done; lim -= done;
@ -426,17 +521,23 @@ void Step()
{ {
lim = 0; lim = 0;
} }
if (State != enter_state)
{
// Finish stats on old state
StepStats.Clock[enter_state].Unclock();
StepStats.BytesCovered[enter_state] += did;
// Start stats on new state
did = 0;
enter_state = State;
StepStats.Clock[enter_state].Clock();
StepStats.Count[enter_state]++;
}
} while (lim && State != GCS_Pause); } while (lim && State != GCS_Pause);
if (State != GCS_Pause)
{ StepStats.Clock[enter_state].Unclock();
Threshold = AllocBytes; StepStats.BytesCovered[enter_state] += did;
} GCTime.Unclock();
else
{
assert(AllocBytes >= Estimate);
SetThreshold();
}
StepCount++;
} }
//========================================================================== //==========================================================================
@ -454,20 +555,23 @@ void FullGC()
// Reset sweep mark to sweep all elements (returning them to white) // Reset sweep mark to sweep all elements (returning them to white)
SweepPos = &Root; SweepPos = &Root;
// Reset other collector lists // Reset other collector lists
Gray = NULL; Gray = nullptr;
State = GCS_Sweep; State = GCS_Sweep;
} }
// Finish any pending sweep phase // Finish any pending GC stages
while (State != GCS_Finalize) while (State != GCS_Pause)
{ {
SingleStep(); SingleStep();
} }
// Loop until everything that can be destroyed and freed is
do
{
MarkRoot(); MarkRoot();
while (State != GCS_Pause) while (State != GCS_Pause)
{ {
SingleStep(); SingleStep();
} }
SetThreshold(); } while (HadToDestroy);
} }
//========================================================================== //==========================================================================
@ -481,9 +585,9 @@ void FullGC()
void Barrier(DObject *pointing, DObject *pointed) void Barrier(DObject *pointing, DObject *pointed)
{ {
assert(pointing == NULL || (pointing->IsBlack() && !pointing->IsDead())); assert(pointing == nullptr || (pointing->IsBlack() && !pointing->IsDead()));
assert(pointed->IsWhite() && !pointed->IsDead()); assert(pointed->IsWhite() && !pointed->IsDead());
assert(State != GCS_Finalize && State != GCS_Pause); assert(State != GCS_Destroy && State != GCS_Pause);
assert(!(pointed->ObjectFlags & OF_Released)); // if a released object gets here, something must be wrong. assert(!(pointed->ObjectFlags & OF_Released)); // if a released object gets here, something must be wrong.
if (pointed->ObjectFlags & OF_Released) return; // don't do anything with non-GC'd objects. if (pointed->ObjectFlags & OF_Released) return; // don't do anything with non-GC'd objects.
// The invariant only needs to be maintained in the propagate state. // The invariant only needs to be maintained in the propagate state.
@ -495,7 +599,7 @@ void Barrier(DObject *pointing, DObject *pointed)
} }
// In other states, we can mark the pointing object white so this // In other states, we can mark the pointing object white so this
// barrier won't be triggered again, saving a few cycles in the future. // barrier won't be triggered again, saving a few cycles in the future.
else if (pointing != NULL) else if (pointing != nullptr)
{ {
pointing->MakeWhite(); pointing->MakeWhite();
} }
@ -503,13 +607,13 @@ void Barrier(DObject *pointing, DObject *pointed)
void DelSoftRootHead() void DelSoftRootHead()
{ {
if (SoftRoots != NULL) if (SoftRoots != nullptr)
{ {
// Don't let the destructor print a warning message // Don't let the destructor print a warning message
SoftRoots->ObjectFlags |= OF_YesReallyDelete; SoftRoots->ObjectFlags |= OF_YesReallyDelete;
delete SoftRoots; delete SoftRoots;
} }
SoftRoots = NULL; SoftRoots = nullptr;
} }
//========================================================================== //==========================================================================
@ -526,7 +630,7 @@ void AddSoftRoot(DObject *obj)
DObject **probe; DObject **probe;
// Are there any soft roots yet? // Are there any soft roots yet?
if (SoftRoots == NULL) if (SoftRoots == nullptr)
{ {
// Create a new object to root the soft roots off of, and stick // Create a new object to root the soft roots off of, and stick
// it at the end of the object list, so we know that anything // it at the end of the object list, so we know that anything
@ -534,17 +638,17 @@ void AddSoftRoot(DObject *obj)
SoftRoots = Create<DObject>(); SoftRoots = Create<DObject>();
SoftRoots->ObjectFlags |= OF_Fixed; SoftRoots->ObjectFlags |= OF_Fixed;
probe = &Root; probe = &Root;
while (*probe != NULL) while (*probe != nullptr)
{ {
probe = &(*probe)->ObjNext; probe = &(*probe)->ObjNext;
} }
Root = SoftRoots->ObjNext; Root = SoftRoots->ObjNext;
SoftRoots->ObjNext = NULL; SoftRoots->ObjNext = nullptr;
*probe = SoftRoots; *probe = SoftRoots;
} }
// Mark this object as rooted and move it after the SoftRoots marker. // Mark this object as rooted and move it after the SoftRoots marker.
probe = &Root; probe = &Root;
while (*probe != NULL && *probe != obj) while (*probe != nullptr && *probe != obj)
{ {
probe = &(*probe)->ObjNext; probe = &(*probe)->ObjNext;
} }
@ -567,14 +671,14 @@ void DelSoftRoot(DObject *obj)
{ {
DObject **probe; DObject **probe;
if (!(obj->ObjectFlags & OF_Rooted)) if (obj == nullptr || !(obj->ObjectFlags & OF_Rooted))
{ // Not rooted, so nothing to do. { // Not rooted, so nothing to do.
return; return;
} }
obj->ObjectFlags &= ~OF_Rooted; obj->ObjectFlags &= ~OF_Rooted;
// Move object out of the soft roots part of the list. // Move object out of the soft roots part of the list.
probe = &SoftRoots; probe = &SoftRoots;
while (*probe != NULL && *probe != obj) while (*probe != nullptr && *probe != obj)
{ {
probe = &(*probe)->ObjNext; probe = &(*probe)->ObjNext;
} }
@ -588,6 +692,52 @@ void DelSoftRoot(DObject *obj)
} }
//==========================================================================
//
// FAveragizer - Constructor
//
//==========================================================================
FAveragizer::FAveragizer()
{
NewestPos = 0;
TotalAmount = 0;
TotalCount = 0;
memset(History, 0, sizeof(History));
}
//==========================================================================
//
// FAveragizer :: AddAlloc
//
//==========================================================================
void FAveragizer::AddAlloc(size_t alloc)
{
NewestPos = (NewestPos + 1) & (HistorySize - 1);
if (TotalCount < HistorySize)
{
TotalCount++;
}
else
{
TotalAmount -= History[NewestPos];
}
History[NewestPos] = alloc;
TotalAmount += alloc;
}
//==========================================================================
//
// FAveragizer :: GetAverage
//
//==========================================================================
size_t FAveragizer::GetAverage()
{
return TotalCount != 0 ? TotalAmount / TotalCount : 0;
}
//========================================================================== //==========================================================================
// //
// STAT gc // STAT gc
@ -602,18 +752,66 @@ ADD_STAT(gc)
" Pause ", " Pause ",
"Propagate", "Propagate",
" Sweep ", " Sweep ",
"Finalize " }; " Destroy ",
" Done "
};
FString out; FString out;
out.Format("[%s] Alloc:%6zuK Thresh:%6zuK Est:%6zuK Steps: %d %zuK", double time = GC::State != GC::GCS_Pause ? GC::GCTime.TimeMS() : 0;
GC::PrevStepStats.Format(out);
out << "\n";
GC::StepStats.Format(out);
out.AppendFormat("\n%.2fms [%s] Rate:%3zuK (%3zuK) Alloc:%6zuK Est:%6zuK Thresh:%6zuK",
time,
StateStrings[GC::State], StateStrings[GC::State],
(GC::AllocHistory.GetAverage() + 1023) >> 10,
(GC::CalcStepSize() + 1023) >> 10,
(GC::AllocBytes + 1023) >> 10, (GC::AllocBytes + 1023) >> 10,
(GC::Threshold + 1023) >> 10,
(GC::Estimate + 1023) >> 10, (GC::Estimate + 1023) >> 10,
GC::StepCount, (GC::Threshold + 1023) >> 10);
(GC::MinStepSize + 1023) >> 10);
return out; return out;
} }
//==========================================================================
//
// FStepStats :: Reset
//
//==========================================================================
void FStepStats::Reset()
{
for (int i = 0; i < countof(Count); ++i)
{
Count[i] = 0;
BytesCovered[i] = 0;
Clock[i].Reset();
}
}
//==========================================================================
//
// FStepStats :: Format
//
// Appends its stats to the given FString.
//
//==========================================================================
void FStepStats::Format(FString &out)
{
// Because everything in the default green is hard to distinguish,
// each stage has its own color.
for (int i = GC::GCS_Propagate; i < GC::GCS_Done; ++i)
{
int count = Count[i];
double time = Clock[i].TimeMS();
out.AppendFormat(TEXTCOLOR_ESCAPESTR "%c[%c%6zuK %4d*%.2fms]",
"-NKB"[i], /* Color codes */
"-PSD"[i], /* Stage prefixes: (P)ropagate, (S)weep, (D)estroy */
(BytesCovered[i] + 1023) >> 10, count, count != 0 ? time / count : time);
}
out << TEXTCOLOR_GREEN;
}
//========================================================================== //==========================================================================
// //
// CCMD gc // CCMD gc

View file

@ -37,12 +37,21 @@ namespace GC
GCS_Pause, GCS_Pause,
GCS_Propagate, GCS_Propagate,
GCS_Sweep, GCS_Sweep,
GCS_Finalize GCS_Destroy,
GCS_Done,
GCS_COUNT
}; };
// Number of bytes currently allocated through M_Malloc/M_Realloc. // Number of bytes currently allocated through M_Malloc/M_Realloc.
extern size_t AllocBytes; extern size_t AllocBytes;
// Number of bytes allocated since last collection step.
extern size_t RunningAllocBytes;
// Number of bytes freed since last collection step.
extern size_t RunningDeallocBytes;
// Amount of memory to allocate before triggering a collection. // Amount of memory to allocate before triggering a collection.
extern size_t Threshold; extern size_t Threshold;
@ -70,18 +79,12 @@ namespace GC
// Is this the final collection just before exit? // Is this the final collection just before exit?
extern bool FinalGC; extern bool FinalGC;
// Counts the number of times CheckGC has been called.
extern uint64_t CheckTime;
// Current white value for known-dead objects. // Current white value for known-dead objects.
static inline uint32_t OtherWhite() static inline uint32_t OtherWhite()
{ {
return CurrentWhite ^ OF_WhiteBits; return CurrentWhite ^ OF_WhiteBits;
} }
// Frees all objects, whether they're dead or not.
void FreeAll();
// Does one collection step. // Does one collection step.
void Step(); void Step();
@ -118,12 +121,7 @@ namespace GC
} }
// Check if it's time to collect, and do a collection step if it is. // Check if it's time to collect, and do a collection step if it is.
static inline void CheckGC() void CheckGC();
{
CheckTime++;
if (AllocBytes >= Threshold)
Step();
}
// Forces a collection to start now. // Forces a collection to start now.
static inline void StartCollection() static inline void StartCollection()
@ -176,6 +174,32 @@ namespace GC
using GCMarkerFunc = void(*)(); using GCMarkerFunc = void(*)();
void AddMarkerFunc(GCMarkerFunc func); void AddMarkerFunc(GCMarkerFunc func);
// Report an allocation to the GC
static inline void ReportAlloc(size_t alloc)
{
AllocBytes += alloc;
RunningAllocBytes += alloc;
}
// Report a deallocation to the GC
static inline void ReportDealloc(size_t dealloc)
{
AllocBytes -= dealloc;
RunningDeallocBytes += dealloc;
}
// Report a reallocation to the GC
static inline void ReportRealloc(size_t oldsize, size_t newsize)
{
if (oldsize < newsize)
{
ReportAlloc(newsize - oldsize);
}
else
{
ReportDealloc(oldsize - newsize);
}
}
} }
// A template class to help with handling read barriers. It does not // A template class to help with handling read barriers. It does not

View file

@ -3,7 +3,7 @@
** Wrappers for the malloc family of functions that count used bytes. ** Wrappers for the malloc family of functions that count used bytes.
** **
**--------------------------------------------------------------------------- **---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit ** Copyright 1998-2008 Marisa Heit
** All rights reserved. ** All rights reserved.
** **
** Redistribution and use in source and binary forms, with or without ** Redistribution and use in source and binary forms, with or without
@ -45,7 +45,7 @@
#endif #endif
#include "engineerrors.h" #include "engineerrors.h"
#include "dobject.h" #include "dobjgc.h"
#ifndef _MSC_VER #ifndef _MSC_VER
#define _NORMAL_BLOCK 0 #define _NORMAL_BLOCK 0
@ -59,25 +59,22 @@ void *M_Malloc(size_t size)
{ {
void *block = malloc(size); void *block = malloc(size);
if (block == NULL) if (block == nullptr)
I_FatalError("Could not malloc %zu bytes", size); I_FatalError("Could not malloc %zu bytes", size);
GC::AllocBytes += _msize(block); GC::ReportAlloc(_msize(block));
return block; return block;
} }
void *M_Realloc(void *memblock, size_t size) void *M_Realloc(void *memblock, size_t size)
{ {
if (memblock != NULL) size_t oldsize = memblock ? _msize(memblock) : 0;
{
GC::AllocBytes -= _msize(memblock);
}
void *block = realloc(memblock, size); void *block = realloc(memblock, size);
if (block == NULL) if (block == nullptr)
{ {
I_FatalError("Could not realloc %zu bytes", size); I_FatalError("Could not realloc %zu bytes", size);
} }
GC::AllocBytes += _msize(block); GC::ReportRealloc(oldsize, _msize(block));
return block; return block;
} }
#else #else
@ -85,28 +82,25 @@ void *M_Malloc(size_t size)
{ {
void *block = malloc(size+sizeof(size_t)); void *block = malloc(size+sizeof(size_t));
if (block == NULL) if (block == nullptr)
I_FatalError("Could not malloc %zu bytes", size); I_FatalError("Could not malloc %zu bytes", size);
size_t *sizeStore = (size_t *) block; size_t *sizeStore = (size_t *) block;
*sizeStore = size; *sizeStore = size;
block = sizeStore+1; block = sizeStore+1;
GC::AllocBytes += _msize(block); GC::ReportAlloc(_msize(block));
return block; return block;
} }
void *M_Realloc(void *memblock, size_t size) void *M_Realloc(void *memblock, size_t size)
{ {
if(memblock == NULL) if (memblock == nullptr)
return M_Malloc(size); return M_Malloc(size);
if (memblock != NULL) size_t oldsize = _msize(memblock);
{
GC::AllocBytes -= _msize(memblock);
}
void *block = realloc(((size_t*) memblock)-1, size+sizeof(size_t)); void *block = realloc(((size_t*) memblock)-1, size+sizeof(size_t));
if (block == NULL) if (block == nullptr)
{ {
I_FatalError("Could not realloc %zu bytes", size); I_FatalError("Could not realloc %zu bytes", size);
} }
@ -115,7 +109,7 @@ void *M_Realloc(void *memblock, size_t size)
*sizeStore = size; *sizeStore = size;
block = sizeStore+1; block = sizeStore+1;
GC::AllocBytes += _msize(block); GC::ReportRealloc(oldsize, _msize(block));
return block; return block;
} }
#endif #endif
@ -129,25 +123,22 @@ void *M_Malloc_Dbg(size_t size, const char *file, int lineno)
{ {
void *block = _malloc_dbg(size, _NORMAL_BLOCK, file, lineno); void *block = _malloc_dbg(size, _NORMAL_BLOCK, file, lineno);
if (block == NULL) if (block == nullptr)
I_FatalError("Could not malloc %zu bytes in %s, line %d", size, file, lineno); I_FatalError("Could not malloc %zu bytes in %s, line %d", size, file, lineno);
GC::AllocBytes += _msize(block); GC::ReportAlloc(_msize(block));
return block; return block;
} }
void *M_Realloc_Dbg(void *memblock, size_t size, const char *file, int lineno) void *M_Realloc_Dbg(void *memblock, size_t size, const char *file, int lineno)
{ {
if (memblock != NULL) size_t oldsize = memblock ? _msize(memblock) : 0;
{
GC::AllocBytes -= _msize(memblock);
}
void *block = _realloc_dbg(memblock, size, _NORMAL_BLOCK, file, lineno); void *block = _realloc_dbg(memblock, size, _NORMAL_BLOCK, file, lineno);
if (block == NULL) if (block == nullptr)
{ {
I_FatalError("Could not realloc %zu bytes in %s, line %d", size, file, lineno); I_FatalError("Could not realloc %zu bytes in %s, line %d", size, file, lineno);
} }
GC::AllocBytes += _msize(block); GC::ReportRealloc(oldsize, _msize(block));
return block; return block;
} }
#else #else
@ -155,29 +146,26 @@ void *M_Malloc_Dbg(size_t size, const char *file, int lineno)
{ {
void *block = _malloc_dbg(size+sizeof(size_t), _NORMAL_BLOCK, file, lineno); void *block = _malloc_dbg(size+sizeof(size_t), _NORMAL_BLOCK, file, lineno);
if (block == NULL) if (block == nullptr)
I_FatalError("Could not malloc %zu bytes in %s, line %d", size, file, lineno); I_FatalError("Could not malloc %zu bytes in %s, line %d", size, file, lineno);
size_t *sizeStore = (size_t *) block; size_t *sizeStore = (size_t *) block;
*sizeStore = size; *sizeStore = size;
block = sizeStore+1; block = sizeStore+1;
GC::AllocBytes += _msize(block); GC::ReportAlloc(_msize(block));
return block; return block;
} }
void *M_Realloc_Dbg(void *memblock, size_t size, const char *file, int lineno) void *M_Realloc_Dbg(void *memblock, size_t size, const char *file, int lineno)
{ {
if(memblock == NULL) if (memblock == nullptr)
return M_Malloc_Dbg(size, file, lineno); return M_Malloc_Dbg(size, file, lineno);
if (memblock != NULL) size_t oldsize = _msize(memblock);
{
GC::AllocBytes -= _msize(memblock);
}
void *block = _realloc_dbg(((size_t*) memblock)-1, size+sizeof(size_t), _NORMAL_BLOCK, file, lineno); void *block = _realloc_dbg(((size_t*) memblock)-1, size+sizeof(size_t), _NORMAL_BLOCK, file, lineno);
if (block == NULL) if (block == nullptr)
{ {
I_FatalError("Could not realloc %zu bytes in %s, line %d", size, file, lineno); I_FatalError("Could not realloc %zu bytes in %s, line %d", size, file, lineno);
} }
@ -186,29 +174,22 @@ void *M_Realloc_Dbg(void *memblock, size_t size, const char *file, int lineno)
*sizeStore = size; *sizeStore = size;
block = sizeStore+1; block = sizeStore+1;
GC::AllocBytes += _msize(block); GC::ReportRealloc(oldsize, _msize(block));
return block; return block;
} }
#endif #endif
#endif #endif
void M_Free (void *block)
{
if (block != nullptr)
{
GC::ReportDealloc(_msize(block));
#if !defined(__solaris__) && !defined(__OpenBSD__) && !defined(__DragonFly__) #if !defined(__solaris__) && !defined(__OpenBSD__) && !defined(__DragonFly__)
void M_Free (void *block)
{
if (block != NULL)
{
GC::AllocBytes -= _msize(block);
free(block); free(block);
}
}
#else #else
void M_Free (void *block)
{
if(block != NULL)
{
GC::AllocBytes -= _msize(block);
free(((size_t*) block)-1); free(((size_t*) block)-1);
#endif
} }
} }
#endif