Commit dbef766c authored by simonmar's avatar simonmar

[project @ 2001-11-26 16:54:21 by simonmar]

Profiling cleanup.

This commit eliminates some duplication in the various heap profiling
subsystems, and generally centralises much of the machinery.  The key
concept is the separation of a heap *census* (which is now done in one
place only instead of three) from the calculation of retainer sets.
Previously the retainer profiling code also did a heap census on the
fly, and lag-drag-void profiling had its own census machinery.

Value-adds:

   - you can now restrict a heap profile to certain retainer sets,
     but still display by cost centre (or type, or closure or
     whatever).

   - I've added an option to restrict the maximum retainer set size
     (+RTS -R<size>, defaulting to 8).

   - I've cleaned up the heap profiling options at the request of
     Simon PJ.  See the help text for details.  The new scheme
     is backwards compatible with the old.

   - I've removed some odd bits of LDV or retainer profiling-specific
     code from various parts of the system.

   - the time taken doing heap censuses (and retainer set calculation)
     is now accurately reported by the RTS when you say +RTS -Sstderr.

Still to come:

   - restricting a profile to a particular biography
     (lag/drag/void/use).  This requires keeping old heap censuses
     around, but the infrastructure is now in place to do this.
parent 5680ea4b
/* ----------------------------------------------------------------------------
* $Id: Closures.h,v 1.29 2001/11/22 14:25:11 simonmar Exp $
* $Id: Closures.h,v 1.30 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 1998-1999
*
......@@ -22,7 +22,7 @@
typedef struct {
CostCentreStack *ccs;
union {
RetainerSet *rs; // Retainer Set
struct _RetainerSet *rs; // Retainer Set
StgWord ldvw; // Lag/Drag/Void Word
} hp;
} StgProfHeader;
......
/* ----------------------------------------------------------------------------
* $Id: Constants.h,v 1.18 2001/10/03 13:57:42 simonmar Exp $
* $Id: Constants.h,v 1.19 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 1998-1999
*
......@@ -144,7 +144,7 @@
-------------------------------------------------------------------------- */
/* The size of a block (2^BLOCK_SHIFT bytes) */
#define BLOCK_SHIFT 12
#define BLOCK_SHIFT 11
/* The size of a megablock (2^MBLOCK_SHIFT bytes) */
#define MBLOCK_SHIFT 20
......
/* -----------------------------------------------------------------------------
* $Id: RtsFlags.h,v 1.39 2001/11/25 16:57:38 sof Exp $
* $Id: RtsFlags.h,v 1.40 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 1998-1999
*
......@@ -97,22 +97,19 @@ struct PROFILING_FLAGS {
# define HEAP_BY_MOD 2
# define HEAP_BY_DESCR 4
# define HEAP_BY_TYPE 5
/* Flags for retainer and lag-drag-void profiling */
# define HEAP_BY_RETAINER 6
# define HEAP_BY_LDV 7
rtsBool showCCSOnException;
# define CCchar 'C'
# define MODchar 'M'
# define DESCRchar 'D'
# define TYPEchar 'Y'
nat maxRetainerSetSize;
char* modSelector;
char* descrSelector;
char* typeSelector;
char* ccSelector;
char* retainerSelector;
char* bioSelector;
};
#elif defined(DEBUG)
......
/* -----------------------------------------------------------------------------
* $Id: Stg.h,v 1.41 2001/11/25 03:56:39 sof Exp $
* $Id: Stg.h,v 1.42 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 1998-1999
*
......@@ -158,7 +158,6 @@ typedef StgWord64 LW_;
/* Profiling information */
#include "StgProf.h"
#include "StgRetainerProf.h"
#include "StgLdvProf.h"
/* Storage format definitions */
......
/* -----------------------------------------------------------------------------
* $Id: StgLdvProf.h,v 1.1 2001/11/22 14:25:11 simonmar Exp $
* $Id: StgLdvProf.h,v 1.2 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 2001
* Author: Sungwoo Park
......@@ -11,53 +11,6 @@
#ifndef STGLDVPROF_H
#define STGLDVPROF_H
#ifdef PROFILING
// Engine
// declared in LdvProfile.c
extern nat ldvTime;
// LdvGenInfo stores the statistics for one specific census.
typedef struct {
double time; // the time in MUT time at the corresponding census is made
// We employ int instead of nat, for some values may be negative temporarily,
// e.g., dragNew.
// computed at each census
int inherentlyUsed; // total size of 'inherently used' closures
int notUsed; // total size of 'never used' closures
int used; // total size of 'used at least once' closures
/*
voidNew and dragNew are updated when a closure is destroyed.
For instance, when a 'never used' closure of size s and creation time
t is destroyed at time u, voidNew of eras t through u - 1 is increased
by s.
Likewise, when a 'used at least once' closure of size s and last use time
t is destroyed at time u, dragNew of eras t + 1 through u - 1 is increase
by s.
In our implementation, voidNew and dragNew are computed indirectly: instead
of updating voidNew or dragNew of all intervening eras, we update that
of the end two eras (one is increased and the other is decreased).
*/
int voidNew; // current total size of 'destroyed without being used' closures
int dragNew; // current total size of 'used at least once and waiting to die'
// closures
// computed post-mortem
int voidTotal; // total size of closures in 'void' state
// lagTotal == notUsed - voidTotal // in 'lag' state
int dragTotal; // total size of closures in 'drag' state
// useTotal == used - dragTotal // in 'use' state
} LdvGenInfo;
extern LdvGenInfo *gi;
// retrieves the LDV word from closure c
#define LDVW(c) (((StgClosure *)(c))->header.prof.hp.ldvw)
/*
An LDV word is divided into 3 parts: state bits (LDV_STATE_MASK), creation
time bits (LDV_CREATE_MASK), and last use time bits (LDV_LAST_MASK).
......@@ -78,6 +31,13 @@ extern LdvGenInfo *gi;
#define LDV_STATE_USE 0x40000000
#endif // SIZEOF_VOID_P
#ifdef PROFILING
extern nat era;
// retrieves the LDV word from closure c
#define LDVW(c) (((StgClosure *)(c))->header.prof.hp.ldvw)
// Stores the creation time for closure c.
// This macro is called at the very moment of closure creation.
//
......@@ -86,38 +46,19 @@ extern LdvGenInfo *gi;
// because retainer profiling also expects LDVW(c) to be initialised
// to zero.
#define LDV_recordCreate(c) \
LDVW((c)) = (ldvTime << LDV_SHIFT) | LDV_STATE_CREATE
LDVW((c)) = (era << LDV_SHIFT) | LDV_STATE_CREATE
// Stores the last use time for closure c.
// This macro *must* be called whenever a closure is used, that is, it is
// entered.
#define LDV_recordUse(c) \
{ \
if (ldvTime > 0) \
LDVW((c)) = (LDVW((c)) & LDV_CREATE_MASK) | \
ldvTime | \
LDV_STATE_USE; \
#define LDV_recordUse(c) \
{ \
if (era > 0) \
LDVW((c)) = (LDVW((c)) & LDV_CREATE_MASK) | \
era | \
LDV_STATE_USE; \
}
// Creates a 0-filled slop of size 'howManyBackwards' backwards from the
// address 'from'.
//
// Invoked when:
// 1) Hp is incremented and exceeds HpLim (in Updates.hc).
// 2) copypart() is called (in GC.c).
#define FILL_SLOP(from, howManyBackwards) \
if (ldvTime > 0) { \
int i; \
for (i = 0;i < (howManyBackwards); i++) \
((StgWord *)(from))[-i] = 0; \
}
// Informs the LDV profiler that closure c has just been evacuated.
// Evacuated objects are no longer needed, so we just store its original size in
// the LDV field.
#define SET_EVACUAEE_FOR_LDV(c, size) \
LDVW((c)) = (size)
// Macros called when a closure is entered.
// The closure is not an 'inherently used' one.
// The closure is not IND or IND_OLDGEN because neither is considered for LDV
......
/* -----------------------------------------------------------------------------
* $Id: StgProf.h,v 1.14 2001/11/22 14:25:11 simonmar Exp $
* $Id: StgProf.h,v 1.15 2001/11/26 16:54:22 simonmar Exp $
*
* (c) The GHC Team, 1998
*
......@@ -39,7 +39,6 @@ typedef struct _CostCentreStack {
unsigned long time_ticks;
unsigned long long mem_alloc;
unsigned long mem_resid;
unsigned long inherited_ticks;
unsigned long long inherited_alloc;
......@@ -184,7 +183,6 @@ extern CostCentreStack *CCS_LIST; /* registered CCS list */
scc_count : 0, \
time_ticks : 0, \
mem_alloc : 0, \
mem_resid : 0, \
inherited_ticks : 0, \
inherited_alloc : 0, \
root : 0, \
......
/* -----------------------------------------------------------------------------
* $Id: StgRetainerProf.h,v 1.1 2001/11/22 14:25:12 simonmar Exp $
*
* (c) The GHC Team, 2001
*
* Retainer profiling
* ---------------------------------------------------------------------------*/
#ifndef STGRETAINERPROF_H
#define STGRETAINERPROF_H
/*
Type 'retainer' defines the retainer identity.
Invariant:
1. The retainer identity of a given retainer cannot change during
program execution, no matter where it is actually stored.
For instance, the memory address of a retainer cannot be used as
its retainer identity because its location may change during garbage
collections.
2. Type 'retainer' must come with comparison operations as well as
an equality operation. That it, <, >, and == must be supported -
this is necessary to store retainers in a sorted order in retainer sets.
Therefore, you cannot use a huge structure type as 'retainer', for instance.
We illustrate three possibilities of defining 'retainer identity'.
Choose one of the following three compiler directives:
Retainer scheme 1 (RETAINER_SCHEME_INFO) : retainer = info table
Retainer scheme 2 (RETAINER_SCHEME_CCS) : retainer = cost centre stack
Retainer scheme 3 (RETAINER_SCHEME_CC) : retainer = cost centre
*/
// #define RETAINER_SCHEME_INFO
#define RETAINER_SCHEME_CCS
// #define RETAINER_SCHEME_CC
#ifdef RETAINER_SCHEME_INFO
struct _StgInfoTable;
typedef struct _StgInfoTable *retainer;
#endif
#ifdef RETAINER_SCHEME_CCS
typedef CostCentreStack *retainer;
#endif
#ifdef RETAINER_SCHEME_CC
typedef CostCentre *retainer;
#endif
/*
Type 'retainerSet' defines an abstract datatype for sets of retainers.
Invariants:
A retainer set stores its elements in increasing order (in element[] array).
*/
typedef struct _RetainerSet {
nat num; // number of elements
nat cost; // cost associated with this retainer set
StgWord hashKey; // hash key for this retainer set
struct _RetainerSet *link; // link to the next retainer set in the bucket
int id; // unique id of this retainer set (used when printing)
// Its absolute value is interpreted as its true id; if id is
// negative, it indicates that this retainer set has had a postive
// cost after some retainer profiling.
retainer element[0]; // elements of this retainer set
// do not put anything below here!
} RetainerSet;
//
// retainerSet - interface: see rts/RetainerSet.h
//
#endif /* STGRETAINERPROF_H */
/* -----------------------------------------------------------------------------
* $Id: GC.c,v 1.127 2001/11/22 14:25:12 simonmar Exp $
* $Id: GC.c,v 1.128 2001/11/26 16:54:21 simonmar Exp $
*
* (c) The GHC Team 1998-1999
*
......@@ -1320,7 +1320,6 @@ copy(StgClosure *src, nat size, step *stp)
stp->hp = to;
upd_evacuee(src,(StgClosure *)dest);
#ifdef PROFILING
// @LDV profiling
// We store the size of the just evacuated object in the LDV word so that
// the profiler can guess the position of the next object later.
SET_EVACUAEE_FOR_LDV(src, size_org);
......@@ -1364,7 +1363,6 @@ copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, step *stp)
stp->hp += size_to_reserve;
upd_evacuee(src,(StgClosure *)dest);
#ifdef PROFILING
// @LDV profiling
// We store the size of the just evacuated object in the LDV word so that
// the profiler can guess the position of the next object later.
// size_to_copy_org is wrong because the closure already occupies size_to_reserve
......
/* -----------------------------------------------------------------------------
* $Id: LdvProfile.c,v 1.1 2001/11/22 14:25:12 simonmar Exp $
* $Id: LdvProfile.c,v 1.2 2001/11/26 16:54:21 simonmar Exp $
*
* (c) The GHC Team, 2001
* Author: Sungwoo Park
......@@ -22,42 +22,10 @@
#include "RtsUtils.h"
#include "Schedule.h"
/*
ldvTime stores the current LDV time, that is, the current era. It
is one larger than the number of times LDV profiling has been
performed, i.e.,
ldvTime - 1 == the number of time LDV profiling was executed
== the number of censuses made so far.
RESTRICTION:
ldvTime must be no longer than LDV_SHIFT (15 or 30) bits.
Invariants:
LDV profiling is turned off if ldvTime is 0.
LDV profiling is turned on if ldvTime is > 0.
ldvTime is initialized to 1 in initLdvProfiling().
If LDV profiling is not performed, ldvTime must remain 0 (e.g., when we
are doing retainer profiling).
ldvTime is set to 1 in initLdvProfiling().
ldvTime is set back to 0 in shutdownHaskell().
In the meanwhile, ldvTime increments.
*/
nat ldvTime = 0;
#
// ldvTimeSave is set in LdvCensusKillAll(), and stores the final number of
// times that LDV profiling was proformed.
static nat ldvTimeSave;
// gi[] stores the statistics obtained at each heap census.
// gi[0] is not used. See initLdvProfiling().
LdvGenInfo *gi;
#define giINCREMENT 32 // allocation unit for gi[]
static nat giLength; // current length of gi[]
// giMax is initialized to 2^LDV_SHIFT in initLdvProfiling().
// When ldvTime reaches giMax, the profiling stops because a closure can
// store only up to (giMax - 1) as its creation or last use time.
static nat giMax;
/* --------------------------------------------------------------------------
* Fills in the slop when a *dynamic* closure changes its type.
* First calls LDV_recordDead() to declare the closure is dead, and then
......@@ -76,7 +44,7 @@ static nat giMax;
void
LDV_recordDead_FILL_SLOP_DYNAMIC( StgClosure *p )
{
if (ldvTime > 0) {
if (era > 0) {
StgInfoTable *inf = get_itbl((p));
nat nw, i;
switch (inf->type) {
......@@ -113,424 +81,6 @@ LDV_recordDead_FILL_SLOP_DYNAMIC( StgClosure *p )
}
}
/* --------------------------------------------------------------------------
* Initialize gi[ldvTime].
* ----------------------------------------------------------------------- */
static inline void
giInitForCurrentEra(void)
{
gi[ldvTime].notUsed = 0;
gi[ldvTime].inherentlyUsed = 0;
gi[ldvTime].used = 0;
gi[ldvTime].voidNew = 0;
gi[ldvTime].dragNew = 0;
}
/* --------------------------------------------------------------------------
* Increases ldvTime by 1 and initialize gi[ldvTime].
* Reallocates gi[] and increases its size if needed.
* ----------------------------------------------------------------------- */
static void
incrementLdvTime( void )
{
ldvTime++;
if (ldvTime == giMax) {
fprintf(stderr,
"Lag/Drag/Void profiling limit %u reached. "
"Please increase the profiling interval with -L option.\n",
giLength);
barf("Current profiling interval = %f seconds",
(float)RtsFlags.ProfFlags.profileInterval / 1000.0 );
}
if (ldvTime % giINCREMENT == 0) {
gi = stgReallocBytes(gi, sizeof(LdvGenInfo) * (giLength + giINCREMENT),
"incrementLdvTime");
giLength += giINCREMENT;
}
// What a stupid bug I struggled against for such a long time! I
// placed giInitForCurrentEra() before the above rellocation part,
// and it cost me three hours!
giInitForCurrentEra();
}
/* --------------------------------------------------------------------------
* Initialization code for LDV profiling.
* ----------------------------------------------------------------------- */
void
initLdvProfiling( void )
{
nat p;
gi = stgMallocBytes(sizeof(LdvGenInfo) * giINCREMENT, "initLdvProfiling");
giLength = giINCREMENT;
ldvTime = 1; // turn on LDV profiling.
giInitForCurrentEra();
// giMax = 2^LDV_SHIFT
giMax = 1;
for (p = 0; p < LDV_SHIFT; p++)
giMax *= 2;
}
/* --------------------------------------------------------------------------
* This function must be called before f-closing prof_file.
* Still hp_file is open; see endHeapProfiling() in ProfHeap.c.
* ----------------------------------------------------------------------- */
void
endLdvProfiling( void )
{
nat t;
int sumVoidNew, sumDragNew;
// Now we compute voidTotal and dragTotal of each LdvGenInfo structure.
sumVoidNew = 0;
sumDragNew = 0;
for (t = 0; t < ldvTimeSave; t++) {
sumVoidNew += gi[t].voidNew;
sumDragNew += gi[t].dragNew;
gi[t].voidTotal = sumVoidNew;
gi[t].dragTotal = sumDragNew;
}
// t = 0 is wrong (because ldvTime == 0 indicates LDV profiling is
// turned off.
for (t = 1;t < ldvTimeSave; t++) {
fprintf(hp_file, "MARK %f\n", gi[t].time);
fprintf(hp_file, "BEGIN_SAMPLE %f\n", gi[t].time);
fprintf(hp_file, "VOID\t%u\n", gi[t].voidTotal * sizeof(StgWord));
fprintf(hp_file, "LAG\t%u\n", (gi[t].notUsed - gi[t].voidTotal) * sizeof(StgWord));
fprintf(hp_file, "USE\t%u\n", (gi[t].used - gi[t].dragTotal) * sizeof(StgWord));
fprintf(hp_file, "INHERENT_USE\t%u\n", gi[t].inherentlyUsed * sizeof(StgWord));
fprintf(hp_file, "DRAG\t%u\n", gi[t].dragTotal * sizeof(StgWord));
fprintf(hp_file, "END_SAMPLE %f\n", gi[t].time);
}
}
/* --------------------------------------------------------------------------
* Print the statistics.
* This function is called after each retainer profiling.
* ----------------------------------------------------------------------- */
static void
outputLdvSet( void )
{
}
/* --------------------------------------------------------------------------
* This function is eventually called on every object in the heap
* during a census. Any census is initiated immediately after a major
* garbage collection, and we exploit this fact in the implementation.
* If c is an 'inherently used' closure, gi[ldvTime].inherentlyUsed is
* updated. If c is an ordinary closure, either gi[ldvTime].notUsed or
* gi[ldvTime].used is updated.
* ----------------------------------------------------------------------- */
static inline nat
processHeapClosure(StgClosure *c)
{
nat size;
StgInfoTable *info;
info = get_itbl(c);
ASSERT(
((LDVW(c) & LDV_CREATE_MASK) >> LDV_SHIFT) <= ldvTime &&
((LDVW(c) & LDV_CREATE_MASK) >> LDV_SHIFT) > 0
);
ASSERT(
((LDVW(c) & LDV_STATE_MASK) == LDV_STATE_CREATE) ||
(
(LDVW(c) & LDV_LAST_MASK) <= ldvTime &&
(LDVW(c) & LDV_LAST_MASK) > 0
)
);
switch (info->type) {
/*
'inherently used' cases: add to gi[ldvTime].inherentlyUsed
*/
case TSO:
size = tso_sizeW((StgTSO *)c);
goto inherently_used;
case MVAR:
size = sizeofW(StgMVar);
goto inherently_used;
case MUT_ARR_PTRS:
case MUT_ARR_PTRS_FROZEN:
size = mut_arr_ptrs_sizeW((StgMutArrPtrs *)c);
goto inherently_used;
case ARR_WORDS:
size = arr_words_sizeW((StgArrWords *)c);
goto inherently_used;
case WEAK:
case MUT_VAR:
case MUT_CONS:
case FOREIGN:
case BCO:
case STABLE_NAME:
size = sizeW_fromITBL(info);
goto inherently_used;
/*
ordinary cases: add to gi[ldvTime].notUsed if c is not being used.
add to gi[ldvTime].used if c is being used.
*/
case THUNK:
size = stg_max(sizeW_fromITBL(info), sizeofW(StgHeader) + MIN_UPD_SIZE);
break;
case THUNK_1_0:
case THUNK_0_1:
case THUNK_2_0:
case THUNK_1_1:
case THUNK_0_2:
case THUNK_SELECTOR:
size = sizeofW(StgHeader) + MIN_UPD_SIZE;
break;
case AP_UPD:
case PAP:
size = pap_sizeW((StgPAP *)c);
break;
case CONSTR:
case CONSTR_1_0:
case CONSTR_0_1:
case CONSTR_2_0:
case CONSTR_1_1:
case CONSTR_0_2:
case FUN:
case FUN_1_0:
case FUN_0_1:
case FUN_2_0:
case FUN_1_1:
case FUN_0_2:
case BLACKHOLE_BQ:
case BLACKHOLE:
case SE_BLACKHOLE:
case CAF_BLACKHOLE:
case SE_CAF_BLACKHOLE:
size = sizeW_fromITBL(info);
break;
case IND_PERM:
size = sizeofW(StgInd);
break;
case IND_OLDGEN_PERM:
size = sizeofW(StgIndOldGen);
break;
/*
Error case
*/
case IND: // IND cannot appear after major GCs.
case IND_OLDGEN: // IND_OLDGEN cannot appear major GCs.
case EVACUATED: // EVACUATED is encountered only during GCs.
// static objects
case IND_STATIC:
case CONSTR_STATIC:
case FUN_STATIC:
case THUNK_STATIC:
case CONSTR_INTLIKE:
case CONSTR_CHARLIKE:
case CONSTR_NOCAF_STATIC:
// stack objects
case UPDATE_FRAME:
case CATCH_FRAME:
case STOP_FRAME:
case SEQ_FRAME:
case RET_DYN:
case RET_BCO:
case RET_SMALL:
case RET_VEC_SMALL:
case RET_BIG:
case RET_VEC_BIG:
// others
case BLOCKED_FETCH:
case FETCH_ME:
case FETCH_ME_BQ:
case RBH:
case REMOTE_REF:
case INVALID_OBJECT:
default:
barf("Invalid object in processHeapClosure(): %d", info->type);
return 0;
}
/*
ordinary cases:
We can compute either gi[ldvTime].notUsed or gi[ldvTime].used; the other
can be computed from the total sum of costs.
At the moment, we choose to compute gi[ldvTime].notUsed, which seems to
be smaller than gi[ldvTime].used.
*/
// ignore closures that don't satisfy our constraints.
if (closureSatisfiesConstraints(c)) {
if ((LDVW(c) & LDV_STATE_MASK) == LDV_STATE_CREATE)
gi[ldvTime].notUsed += size - sizeofW(StgProfHeader);
else
gi[ldvTime].used += size - sizeofW(StgProfHeader);
}
return size;
inherently_used:
// ignore closures that don't satisfy our constraints.
if (closureSatisfiesConstraints(c)) {
gi[ldvTime].inherentlyUsed += size - sizeofW(StgProfHeader);
}
return size;
}
/* --------------------------------------------------------------------------
* Calls processHeapClosure() on every closure in the heap blocks
* begining at bd during a census.
* ----------------------------------------------------------------------- */
static void
processHeap( bdescr *bd )
{
StgPtr p;
nat size;
while (bd != NULL) {
p = bd->start;
while (p < bd->free) {
size = processHeapClosure((StgClosure *)p);
p += size;
while (p < bd->free && !*p) // skip slop
p++;
}
ASSERT(p == bd->free);
bd = bd->link;
}
}
/* --------------------------------------------------------------------------
* Calls processHeapClosure() on every closure in the small object pool
* during a census.
* ----------------------------------------------------------------------- */
static void
processSmallObjectPool( void )
{
bdescr *bd;
StgPtr p;
nat size;
bd = small_alloc_list;
// first block
if (bd == NULL)
return;
p = bd->start;
while (p < alloc_Hp) {
size = processHeapClosure((StgClosure *)p);
p += size;
while (p < alloc_Hp && !*p) // skip slop
p++;
}
ASSERT(p == alloc_Hp);
bd = bd->link;
while (bd != NULL) {
p = bd->start;
while (p < bd->free) {
size = processHeapClosure((StgClosure *)p);
p += size;
while (p < bd->free && !*p) // skip slop
p++;
}
ASSERT(p == bd->free);
bd = bd->link;
}
}
/* --------------------------------------------------------------------------
* Calls processHeapClosure() on every (large) closure in the object
* chain beginning at bd during a census.
* ----------------------------------------------------------------------- */
static void
processChain( bdescr *bd )
{
while (bd != NULL) {
// bd->free - bd->start is not an accurate measurement of the
// object size. Actually it is always zero, so we compute its
// size explicitly.
processHeapClosure((StgClosure *)bd->start);
bd = bd->link;
}
}
/* --------------------------------------------------------------------------
* Starts a census for LDV profiling.
* Invariants:
* Any call to LdvCensus() is preceded by a major garbage collection.
* ----------------------------------------------------------------------- */
void
LdvCensus( void )
{
nat g, s;
// ldvTime == 0 means that LDV profiling is currently turned off.
if (ldvTime == 0)
return;