code cleanup for mark&sweep GC

This commit is contained in:
Araq 2013-02-07 16:09:46 +01:00
commit 4ee9816a78
7 changed files with 43 additions and 88 deletions

View file

@ -21,7 +21,7 @@ proc registerGcRoot(p: BProc, v: PSym) =
# that it works out of the box for thread local storage then :-) # that it works out of the box for thread local storage then :-)
let prc = genTraverseProcForGlobal(p.module, v) let prc = genTraverseProcForGlobal(p.module, v)
linefmt(p.module.initProc, cpsStmts, linefmt(p.module.initProc, cpsStmts,
"#nimRegisterGlobalMarker((void*)$1);$n", prc) "#nimRegisterGlobalMarker($1);$n", prc)
proc genVarTuple(p: BProc, n: PNode) = proc genVarTuple(p: BProc, n: PNode) =
var tup, field: TLoc var tup, field: TLoc

View file

@ -64,7 +64,8 @@ Advanced options:
--skipUserCfg do not read the user's configuration file --skipUserCfg do not read the user's configuration file
--skipParentCfg do not read the parent dirs' configuration files --skipParentCfg do not read the parent dirs' configuration files
--skipProjCfg do not read the project's configuration file --skipProjCfg do not read the project's configuration file
--gc:refc|v2|boehm|none use Nimrod's native GC|V2|Boehm GC|no GC --gc:refc|v2|markAndSweep|boehm|none
select the GC to use; default is 'refc'
--index:on|off turn index file generation on|off --index:on|off turn index file generation on|off
--putenv:key=value set an environment variable --putenv:key=value set an environment variable
--babelPath:PATH add a path for Babel support --babelPath:PATH add a path for Babel support

View file

@ -68,38 +68,6 @@ proc deinit(s: var TCellSeq) =
s.len = 0 s.len = 0
s.cap = 0 s.cap = 0
# ------------------- cyclic cell temporary data structure --------------------
type
TCycleCell = object
cell: PCell
oldRefcount, newRefcount: TRefCount
PCycleCellArray = ptr array[0..100_000_000, TCycleCell]
TCycleCellSeq {.final, pure.} = object
len, cap: int
d: PCycleCellArray
proc reserveSlot(s: var TCycleCellSeq): int =
if s.len >= s.cap:
s.cap = s.cap * 3 div 2
var d = cast[PCycleCellArray](Alloc(s.cap * sizeof(TCycleCell)))
copyMem(d, s.d, s.len * sizeof(TCycleCell))
Dealloc(s.d)
s.d = d
result = s.len
inc(s.len)
proc init(s: var TCycleCellSeq, cap: int = 1024) =
s.len = 0
s.cap = cap
s.d = cast[PCycleCellArray](Alloc(cap * sizeof(TCycleCell)))
proc deinit(s: var TCycleCellSeq) =
Dealloc(s.d)
s.d = nil
s.len = 0
s.cap = 0
# ------------------- cell set handling --------------------------------------- # ------------------- cell set handling ---------------------------------------
const const
@ -230,7 +198,7 @@ iterator elements(t: TCellSet): PCell {.inline.} =
inc(i) inc(i)
r = r.next r = r.next
iterator elementsWithout(t, s: TCellSet): PCell {.inline.} = iterator elementsExcept(t, s: TCellSet): PCell {.inline.} =
var r = t.head var r = t.head
while r != nil: while r != nil:
let ss = CellSetGet(s, r.key) let ss = CellSetGet(s, r.key)

View file

@ -11,8 +11,9 @@
{.push profiler:off.} {.push profiler:off.}
const const
CycleIncrease = 2 # is a multiplicative increase InitialThreshold = 4*1024*1024 # X MB because marking&sweeping is slow
InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow
template mulThreshold(x): expr {.immediate.} = x * 2
when defined(memProfiler): when defined(memProfiler):
proc nimProfile(requestedSize: int) proc nimProfile(requestedSize: int)
@ -31,12 +32,10 @@ type
TGlobalMarkerProc = proc () {.nimcall.} TGlobalMarkerProc = proc () {.nimcall.}
TGcStat = object TGcStat = object
stackScans: int # number of performed stack scans (for statistics)
collections: int # number of performed full collections collections: int # number of performed full collections
maxThreshold: int # max threshold that has been set maxThreshold: int # max threshold that has been set
maxStackSize: int # max stack size maxStackSize: int # max stack size
maxStackCells: int # max stack cells in ``decStack`` freedObjects: int # max entries in cycle table
cycleTableSize: int # max entries in cycle table
TGcHeap = object # this contains the zero count and TGcHeap = object # this contains the zero count and
# non-zero count table # non-zero count table
@ -87,33 +86,24 @@ proc unsureAsgnRef(dest: ppointer, src: pointer) {.inline.} =
dest[] = src dest[] = src
proc internRefcount(p: pointer): int {.exportc: "getRefcount".} = proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
result = int(usrToCell(p).refcount) result = 0
var var
globalMarkersLen: int globalMarkersLen: int
globalMarkers: array[0.. 10_000, TGlobalMarkerProc] globalMarkers: array[0.. 7_000, TGlobalMarkerProc]
proc nimRegisterGlobalMarker(markerProc: pointer) {.compilerProc.} = proc nimRegisterGlobalMarker(markerProc: TGlobalMarkerProc) {.compilerProc.} =
globalMarkers[globalMarkersLen] = cast[TGlobalMarkerProc](markerProc) if globalMarkersLen <= high(globalMarkers):
globalMarkers[globalMarkersLen] = markerProc
inc globalMarkersLen inc globalMarkersLen
else:
echo "[GC] cannot register global variable; too many global variables"
quit 1
# this that has to equals zero, otherwise we have to round up UnitsPerPage: # this that has to equals zero, otherwise we have to round up UnitsPerPage:
when BitsPerPage mod (sizeof(int)*8) != 0: when BitsPerPage mod (sizeof(int)*8) != 0:
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
proc writeCell(msg: CString, c: PCell) =
var kind = -1
if c.typ != nil: kind = ord(c.typ.kind)
when leakDetector:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
msg, c, kind, c.refcount, c.filename, c.line)
else:
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld\n",
msg, c, kind, c.refcount)
template gcTrace(cell, state: expr): stmt {.immediate.} =
when traceGC: traceCell(cell, state)
# forward declarations: # forward declarations:
proc collectCT(gch: var TGcHeap) proc collectCT(gch: var TGcHeap)
proc IsOnStack*(p: pointer): bool {.noinline.} proc IsOnStack*(p: pointer): bool {.noinline.}
@ -133,20 +123,18 @@ proc prepareDealloc(cell: PCell) =
(cast[TFinalizer](cell.typ.finalizer))(cellToUsr(cell)) (cast[TFinalizer](cell.typ.finalizer))(cellToUsr(cell))
dec(gch.recGcLock) dec(gch.recGcLock)
proc nimGCref(p: pointer) {.compilerProc, inline.} = inc(usrToCell(p).refCount) proc nimGCref(p: pointer) {.compilerProc, inline.} =
proc nimGCunref(p: pointer) {.compilerProc, inline.} = dec(usrToCell(p).refCount) # we keep it from being collected by pretending it's not even allocated:
excl(gch.allocated, usrToCell(p))
proc nimGCunref(p: pointer) {.compilerProc, inline.} =
incl(gch.allocated, usrToCell(p))
proc initGC() = proc initGC() =
when not defined(useNimRtl): when not defined(useNimRtl):
when traceGC: gch.cycleThreshold = InitialThreshold
for i in low(TCellState)..high(TCellState): Init(states[i])
gch.cycleThreshold = InitialCycleThreshold
gch.stat.stackScans = 0
gch.stat.collections = 0 gch.stat.collections = 0
gch.stat.maxThreshold = 0 gch.stat.maxThreshold = 0
gch.stat.maxStackSize = 0 gch.stat.maxStackSize = 0
gch.stat.maxStackCells = 0
# init the rt
init(gch.tempStack) init(gch.tempStack)
Init(gch.allocated) Init(gch.allocated)
init(gch.marked) init(gch.marked)
@ -290,30 +278,27 @@ proc doOperation(p: pointer, op: TWalkOp) =
gcAssert(c != nil, "doOperation: 1") gcAssert(c != nil, "doOperation: 1")
case op case op
of waMarkGlobal: of waMarkGlobal:
when hasThreadSupport:
# could point to a cell which we don't own and don't want to touch/trace
if isAllocatedPtr(gch.region, c): if isAllocatedPtr(gch.region, c):
mark(gch, c) mark(gch, c)
else:
mark(gch, c)
of waMarkPrecise: add(gch.tempStack, c) of waMarkPrecise: add(gch.tempStack, c)
proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} =
doOperation(d, TWalkOp(op)) doOperation(d, TWalkOp(op))
proc freeCyclicCell(gch: var TGcHeap, c: PCell) = proc freeCyclicCell(gch: var TGcHeap, c: PCell) =
inc gch.stat.freedObjects
prepareDealloc(c) prepareDealloc(c)
gcTrace(c, csCycFreed)
when logGC: writeCell("cycle collector dealloc cell", c)
when reallyDealloc: rawDealloc(gch.region, c) when reallyDealloc: rawDealloc(gch.region, c)
else: else:
gcAssert(c.typ != nil, "freeCyclicCell") gcAssert(c.typ != nil, "freeCyclicCell")
zeroMem(c, sizeof(TCell)) zeroMem(c, sizeof(TCell))
proc sweep(gch: var TGcHeap) = proc sweep(gch: var TGcHeap) =
when true: for c in gch.allocated.elementsExcept(gch.marked):
for c in gch.allocated.elementsWithout(gch.marked):
gch.allocated.excl(c)
freeCyclicCell(gch, c)
else:
for c in gch.allocated.elements():
if not gch.marked.contains(c):
gch.allocated.excl(c) gch.allocated.excl(c)
freeCyclicCell(gch, c) freeCyclicCell(gch, c)
@ -465,12 +450,10 @@ proc collectCTBody(gch: var TGcHeap) =
markGlobals(gch) markGlobals(gch)
sweep(gch) sweep(gch)
inc(gch.stat.stackScans)
inc(gch.stat.collections) inc(gch.stat.collections)
deinit(gch.marked) deinit(gch.marked)
init(gch.marked) init(gch.marked)
gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * gch.cycleThreshold = max(InitialThreshold, getOccupiedMem().mulThreshold)
cycleIncrease)
gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold) gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold)
sysAssert(allocInv(gch.region), "collectCT: end") sysAssert(allocInv(gch.region), "collectCT: end")
@ -494,7 +477,7 @@ when not defined(useNimRtl):
proc GC_setStrategy(strategy: TGC_Strategy) = nil proc GC_setStrategy(strategy: TGC_Strategy) = nil
proc GC_enableMarkAndSweep() = proc GC_enableMarkAndSweep() =
gch.cycleThreshold = InitialCycleThreshold gch.cycleThreshold = InitialThreshold
proc GC_disableMarkAndSweep() = proc GC_disableMarkAndSweep() =
gch.cycleThreshold = high(gch.cycleThreshold)-1 gch.cycleThreshold = high(gch.cycleThreshold)-1
@ -512,13 +495,10 @@ when not defined(useNimRtl):
GC_disable() GC_disable()
result = "[GC] total memory: " & $getTotalMem() & "\n" & result = "[GC] total memory: " & $getTotalMem() & "\n" &
"[GC] occupied memory: " & $getOccupiedMem() & "\n" & "[GC] occupied memory: " & $getOccupiedMem() & "\n" &
"[GC] stack scans: " & $gch.stat.stackScans & "\n" &
"[GC] stack cells: " & $gch.stat.maxStackCells & "\n" &
"[GC] collections: " & $gch.stat.collections & "\n" & "[GC] collections: " & $gch.stat.collections & "\n" &
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & "[GC] freed objects: " & $gch.stat.freedObjects & "\n" &
"[GC] max stack size: " & $gch.stat.maxStackSize & "\n" "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
when traceGC: writeLeakage()
GC_enable() GC_enable()
{.pop.} {.pop.}

View file

@ -12,7 +12,9 @@ type
proc MakeObj(): TTestObj = proc MakeObj(): TTestObj =
result.x = "Hello" result.x = "Hello"
for i in 1 .. 100_000_000: for i in 1 .. 1_000_000:
when defined(gcMarkAndSweep):
GC_fullcollect()
var obj = MakeObj() var obj = MakeObj()
if getOccupiedMem() > 300_000: quit("still a leak!") if getOccupiedMem() > 300_000: quit("still a leak!")
# echo GC_getstatistics() # echo GC_getstatistics()

View file

@ -15,7 +15,9 @@ proc MakeObj(): TTestObj =
result.s = @[1,2,3] result.s = @[1,2,3]
proc inProc() = proc inProc() =
for i in 1 .. 100_000_000: for i in 1 .. 1_000_000:
when defined(gcMarkAndSweep):
GC_fullcollect()
var obj: TTestObj var obj: TTestObj
obj = MakeObj() obj = MakeObj()
if getOccupiedMem() > 300_000: quit("still a leak!") if getOccupiedMem() > 300_000: quit("still a leak!")

View file

@ -40,6 +40,8 @@ proc main =
for i in 0 .. s.high: for i in 0 .. s.high:
s[i] = register(create()) s[i] = register(create())
# test that we have at least 80% unreachable weak objects by now: # test that we have at least 80% unreachable weak objects by now:
when defined(gcMarkAndSweep):
GC_fullcollect()
var unreachable = 0 var unreachable = 0
for i in 0 .. s.high: for i in 0 .. s.high:
if access(s[i]) == nil: inc unreachable if access(s[i]) == nil: inc unreachable