summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-04-21 03:19:43 +0200
committerAraq <rumpf_a@web.de>2012-04-21 03:19:43 +0200
commit4aba7421f57d0f653ef928f012982957404416f9 (patch)
treed370a057c9f1ec4ffa4f9d16ee2c6c2359e04143
parent8319e2411d07503f8ca1475f1ef9009384560c1c (diff)
downloadNim-4aba7421f57d0f653ef928f012982957404416f9.tar.gz
GC with realtime support
-rwxr-xr-xcompiler/nimrod.nim3
-rwxr-xr-xdoc/docs.txt4
-rw-r--r--doc/gc.txt58
-rwxr-xr-xdoc/nimrodc.txt3
-rwxr-xr-xlib/system/gc.nim105
-rw-r--r--lib/system/timers.nim92
-rwxr-xr-xtests/gc/gcbench.nim3
-rwxr-xr-xtests/gc/gcleak.nim3
-rwxr-xr-xtests/gc/gcleak2.nim3
-rwxr-xr-xtests/gc/gcleak3.nim3
-rw-r--r--tests/specials.nim2
-rwxr-xr-xtodo.txt3
-rwxr-xr-xweb/index.txt2
-rwxr-xr-xweb/news.txt2
-rwxr-xr-xweb/nimrod.ini2
15 files changed, 261 insertions, 27 deletions
diff --git a/compiler/nimrod.nim b/compiler/nimrod.nim
index e9c674a07..6328c3ae3 100755
--- a/compiler/nimrod.nim
+++ b/compiler/nimrod.nim
@@ -103,6 +103,9 @@ proc HandleCmdLine() =
         execExternalProgram(ex & ' ' & arguments)
 
 #GC_disableMarkAndSweep()
+
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
 condsyms.InitDefines()
 HandleCmdLine()
 quit(options.gExitcode)
diff --git a/doc/docs.txt b/doc/docs.txt
index cb9d1f9d6..42294ea1c 100755
--- a/doc/docs.txt
+++ b/doc/docs.txt
@@ -22,6 +22,10 @@ The documentation consists of several documents:
 - | `Manual <manual.html>`_
   | The Nimrod manual is a draft that will evolve into a proper specification.
 
+- | `GC <gc.html>`_
+  | Additional documentation about Nimrod's GC and how to operate it in a
+  | realtime setting.
+
 - | `Source code filters <filters.html>`_
   | The Nimrod compiler supports source code filters as a simple yet powerful
     builtin templating system.
diff --git a/doc/gc.txt b/doc/gc.txt
new file mode 100644
index 000000000..e052135eb
--- /dev/null
+++ b/doc/gc.txt
@@ -0,0 +1,58 @@
+==========================
+Nimrod's Garbage Collector
+==========================
+
+Introduction
+============
+
+This document describes how the GC works and how to tune it for (soft)
+realtime systems.
+
+The basic algorithm is *Deferrent Reference Counting* with cycle detection.
+References on the stack are not counted for better performance (and easier C
+code generation). The GC **never** scans the whole heap but it may scan the
+delta-subgraph of the heap that changed since its last run.
+
+
+The GC is only triggered in a memory allocation operation. It it not triggered
+by some timer or background thread.
+
+
+Realtime support
+================
+
+To enable realtime support, the switch `useRealtimeGC`:idx: needs to be
+defined. With this switch the GC supports the following operations:
+
+.. code-block:: nimrod
+  proc GC_setMaxPause*(MaxPauseInUs: int)
+  proc GC_step*(us: int, strongAdvice = false)
+
+After calling ``GC_setMaxPause`` any GC run tries to finish within 
+``MaxPauseInUs`` microseconds. XXX complete documentation
+
+
+
+Time measurement
+----------------
+
+The GC's way of measing time uses (see ``lib/system/timers.nim`` for the 
+implementation):
+
+1) ``QueryPerformanceCounter`` and ``QueryPerformanceFrequency`` on Windows.
+2) ``mach_absolute_time`` on Mac OS X.
+3) ``gettimeofday`` on Posix systems.
+
+As such it supports a resolution of nano seconds internally; however the API
+uses microseconds for convenience. 
+
+
+Define the symbol ``reportMissedDeadlines`` to make the GC output whenever it
+missed a deadline. The reporting will be enhances and supported by the API in
+later versions of the collector.
+
+
+Tweaking the GC
+===============
+
+To be written. 
diff --git a/doc/nimrodc.txt b/doc/nimrodc.txt
index 151d89b25..9c165d499 100755
--- a/doc/nimrodc.txt
+++ b/doc/nimrodc.txt
@@ -142,6 +142,9 @@ Define               Effect
 ``useNimRtl``        Compile and link against ``nimrtl.dll``.
 ``useMalloc``        Makes Nimrod use C's `malloc`:idx: instead of Nimrod's 
                      own memory manager. This only works with ``gc:none``.
+``useRealtimeGC``    Enables support of Nimrod's GC for *soft* realtime 
+                     systems. See the documentation of the `gc <gc.html>`_ 
+                     for further information.
 ==================   =========================================================
 
 

diff --git a/lib/system/gc.nim b/lib/system/gc.nim
index af54fe350..1fdb1d335 100755
--- a/lib/system/gc.nim
+++ b/lib/system/gc.nim
@@ -15,9 +15,8 @@
 # together with Christoper's partial mark-sweep garbage collector.
 #
 # Special care has been taken to avoid recursion as far as possible to avoid
-# stack overflows when traversing deep datastructures. This is comparable to
-# an incremental and generational GC. It should be well-suited for soft real
-# time applications (like games).
+# stack overflows when traversing deep datastructures. It is well-suited
+# for soft real time applications (like games).
 
 const
   CycleIncrease = 2 # is a multiplicative increase
@@ -25,6 +24,10 @@ const
   ZctThreshold = 500  # we collect garbage if the ZCT's size
                       # reaches this threshold
                       # this seems to be a good value
+  withRealTime = defined(useRealtimeGC)
+
+when withRealTime:
+  include "system/timers"
 
 const
   rcIncrement = 0b1000 # so that lowest 3 bits are not touched
@@ -53,6 +56,7 @@ type
     maxStackSize: int        # max stack size
     maxStackCells: int       # max stack cells in ``decStack``
     cycleTableSize: int      # max entries in cycle table  
+    maxPause: int64          # max measured GC pause in nanoseconds
   
   TGcHeap {.final, pure.} = object # this contains the zero count and
                                    # non-zero count table
@@ -63,6 +67,8 @@ type
     cycleRoots: TCellSet
     tempStack: TCellSeq      # temporary stack for recursion elimination
     recGcLock: int           # prevent recursion via finalizers; no thread lock
+    when withRealTime:
+      maxPause: TNanos       # max allowed pause in nanoseconds; active if > 0
     region: TMemRegion       # garbage collected region
     stat: TGcStat
 
@@ -173,8 +179,6 @@ when traceGC:
 template gcTrace(cell, state: expr): stmt {.immediate.} =
   when traceGC: traceCell(cell, state)
 
-# -----------------------------------------------------------------------------
-
 # forward declarations:
 proc collectCT(gch: var TGcHeap)
 proc IsOnStack*(p: pointer): bool {.noinline.}
@@ -741,12 +745,18 @@ else:
 # end of non-portable code
 # ----------------------------------------------------------------------------
 
-proc CollectZCT(gch: var TGcHeap) =
+proc CollectZCT(gch: var TGcHeap): bool =
   # Note: Freeing may add child objects to the ZCT! So essentially we do 
   # deep freeing, which is bad for incremental operation. In order to 
   # avoid a deep stack, we move objects to keep the ZCT small.
   # This is performance critical!
+  const workPackage = 100
   var L = addr(gch.zct.len)
+  
+  when withRealtime:
+    var steps = workPackage
+    var t0: TTicks
+    if gch.maxPause > 0: t0 = getticks()
   while L[] > 0:
     var c = gch.zct.d[0]
     sysAssert(isAllocatedPtr(gch.region, c), "CollectZCT: isAllocatedPtr")
@@ -756,6 +766,7 @@ proc CollectZCT(gch: var TGcHeap) =
     c.refcount = c.refcount and not colorMask
     gch.zct.d[0] = gch.zct.d[L[] - 1]
     dec(L[])
+    when withRealtime: dec steps
     if c.refcount <% rcIncrement: 
       # It may have a RC > 0, if it is in the hardware stack or
       # it has not been removed yet from the ZCT. This is because
@@ -775,6 +786,17 @@ proc CollectZCT(gch: var TGcHeap) =
       else:
         sysAssert(c.typ != nil, "collectZCT 2")
         zeroMem(c, sizeof(TCell))
+    when withRealtime:
+      if steps == 0:
+        steps = workPackage
+        if gch.maxPause > 0:
+          let duration = getticks() - t0
+          # the GC's measuring is not accurate and needs some cleanup actions 
+          # (stack unmarking), so subtract some short amount of time in to
+          # order to miss deadlines less often:
+          if duration >= gch.maxPause - 50_000:
+            return false
+  result = true
 
 proc unmarkStackAndRegisters(gch: var TGcHeap) = 
   var d = gch.decStack.d
@@ -788,30 +810,64 @@ proc unmarkStackAndRegisters(gch: var TGcHeap) =
     sysAssert c.typ != nil, "unmarkStackAndRegisters 2"
   gch.decStack.len = 0
 
-proc collectCT(gch: var TGcHeap) =
-  if (gch.zct.len >= ZctThreshold or (cycleGC and
-      getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and 
-      gch.recGcLock == 0:
-    sysAssert(allocInv(gch.region), "collectCT: begin")
-    
-    gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
-    sysAssert(gch.decStack.len == 0, "collectCT")
-    prepareForInteriorPointerChecking(gch.region)
-    markStackAndRegisters(gch)
-    markThreadStacks(gch)
-    gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len)
-    inc(gch.stat.stackScans)
-    collectZCT(gch)
+proc collectCTBody(gch: var TGcHeap) =
+  when withRealtime:
+    let t0 = getticks()
+  sysAssert(allocInv(gch.region), "collectCT: begin")
+  
+  gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
+  sysAssert(gch.decStack.len == 0, "collectCT")
+  prepareForInteriorPointerChecking(gch.region)
+  markStackAndRegisters(gch)
+  markThreadStacks(gch)
+  gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len)
+  inc(gch.stat.stackScans)
+  if collectZCT(gch):
     when cycleGC:
       if getOccupiedMem(gch.region) >= gch.cycleThreshold or alwaysCycleGC:
         collectCycles(gch)
-        collectZCT(gch)
+        discard collectZCT(gch)
         inc(gch.stat.cycleCollections)
         gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() *
                                  cycleIncrease)
         gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold)
-    unmarkStackAndRegisters(gch)
-    sysAssert(allocInv(gch.region), "collectCT: end")
+  unmarkStackAndRegisters(gch)
+  sysAssert(allocInv(gch.region), "collectCT: end")
+  
+  when withRealtime:
+    let duration = getticks() - t0
+    gch.stat.maxPause = max(gch.stat.maxPause, duration)
+    when defined(reportMissedDeadlines):
+      if gch.maxPause > 0 and duration > gch.maxPause:
+        c_fprintf(c_stdout, "[GC] missed deadline: %ld\n", duration)
+
+proc collectCT(gch: var TGcHeap) =
+  if (gch.zct.len >= ZctThreshold or (cycleGC and
+      getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and 
+      gch.recGcLock == 0:
+    collectCTBody(gch)
+
+when withRealtime:
+  proc toNano(x: int): TNanos {.inline.} =
+    result = x * 1000
+
+  proc GC_setMaxPause*(MaxPauseInUs: int) =
+    gch.maxPause = MaxPauseInUs.toNano
+
+  proc GC_step(gch: var TGcHeap, us: int, strongAdvice: bool) =
+    acquire(gch)
+    var oldThreshold = gch.cycleThreshold
+    # disable cycle collection:
+    gch.cycleThreshold = high(gch.cycleThreshold)-1
+    gch.maxPause = us.toNano
+    if strongAdvice:
+      if gch.recGcLock == 0: collectCTBody(gch)
+    else:
+      collectCT(gch)
+    gch.cycleThreshold = oldThreshold
+    release(gch)
+
+  proc GC_step*(us: int, strongAdvice = false) = GC_step(gch, us, strongAdvice)
 
 when not defined(useNimRtl):
   proc GC_disable() = 
@@ -858,6 +914,7 @@ when not defined(useNimRtl):
              "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
              "[GC] zct capacity: " & $gch.zct.cap & "\n" &
              "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
-             "[GC] max stack size: " & $gch.stat.maxStackSize
+             "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" &
+             "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000)
     when traceGC: writeLeakage()
     GC_enable()
diff --git a/lib/system/timers.nim b/lib/system/timers.nim
new file mode 100644
index 000000000..0166c1e3f
--- /dev/null
+++ b/lib/system/timers.nim
@@ -0,0 +1,92 @@
+#
+#
+#            Nimrod's Runtime Library
+#        (c) Copyright 2012 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Timer support for the realtime GC. Based on
+## `<https://github.com/jckarter/clay/blob/master/compiler/src/hirestimer.cpp>`_
+
+type
+  TTicks = distinct int64
+  TNanos = int64
+
+when defined(windows):
+
+  proc QueryPerformanceCounter(res: var TTicks) {.
+    importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".}
+  proc QueryPerformanceFrequency(res: var int64) {.
+    importc: "QueryPerformanceFrequency", stdcall, dynlib: "kernel32".}
+
+  proc getTicks(): TTicks {.inline.} =
+    QueryPerformanceCounter(result)
+
+  proc `-`(a, b: TTicks): TNanos =
+    var frequency: int64
+    QueryPerformanceFrequency(frequency)
+    var performanceCounterRate = 1000000000.0 / toFloat(frequency.int)
+
+    result = ((a.int64 - b.int64).int.toFloat * performanceCounterRate).TNanos
+
+elif defined(macosx):
+  type
+    TMachTimebaseInfoData {.pure, final, 
+        importc: "mach_timebase_info_data_t", 
+        header: "<mach/mach_time.h>".} = object
+      numer, denom: int32
+
+  proc mach_absolute_time(): int64 {.importc, header: "<mach/mach.h>".}
+  proc mach_timebase_info(info: var TMachTimebaseInfoData) {.importc,
+    header: "<mach/mach_time.h>".}
+
+  proc getTicks(): TTicks {.inline.} =
+    result = TTicks(mach_absolute_time())
+
+  proc `-`(a, b: TTicks): TNanos =
+    var timeBaseInfo: TMachTimebaseInfoData
+    mach_timebase_info(timeBaseInfo)
+    result = (a.int64 - b.int64)  * timeBaseInfo.numer div timeBaseInfo.denom
+
+elif defined(posixRealtime):
+  type
+    TClockid {.importc: "clockid_t", header: "<time.h>", final.} = object
+
+    TTimeSpec {.importc: "struct timespec", header: "<time.h>", 
+               final, pure.} = object ## struct timespec
+      tv_sec: int  ## Seconds. 
+      tv_nsec: int ## Nanoseconds. 
+
+  var
+    CLOCK_REALTIME {.importc: "CLOCK_REALTIME", header: "<time.h>".}: TClockid
+
+  proc clock_gettime(clkId: TClockid, tp: var TTimespec) {.
+    importc: "clock_gettime", header: "<time.h>".}
+
+  proc getTicks(): TTicks =
+    var t: TTimespec
+    clock_gettime(CLOCK_REALTIME, t)
+    result = TTicks(int64(t.tv_sec) * 1000000000'i64 + int64(t.tv_nsec))
+
+  proc `-`(a, b: TTicks): TNanos {.borrow.}
+
+else:
+  # fallback Posix implementation:  
+  type
+    Ttimeval {.importc: "struct timeval", header: "<sys/select.h>", 
+               final, pure.} = object ## struct timeval
+      tv_sec: int  ## Seconds. 
+      tv_usec: int ## Microseconds. 
+        
+  proc posix_gettimeofday(tp: var Ttimeval, unused: pointer = nil) {.
+    importc: "gettimeofday", header: "<sys/time.h>".}
+
+  proc getTicks(): TTicks =
+    var t: Ttimeval
+    posix_gettimeofday(t)
+    result = TTicks(int64(t.tv_sec) * 1000_000_000'i64 + 
+                    int64(t.tv_usec) * 1000'i64)
+
+  proc `-`(a, b: TTicks): TNanos {.borrow.}
diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim
index 253b74805..44d952baa 100755
--- a/tests/gc/gcbench.nim
+++ b/tests/gc/gcbench.nim
@@ -161,5 +161,8 @@ proc main() =
   var elapsed = epochTime() - t

   PrintDiagnostics()

   echo("Completed in " & $elapsed & "ms. Success!")

+
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
 

 main()

diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim
index ab85409ee..c40c6b3b5 100755
--- a/tests/gc/gcleak.nim
+++ b/tests/gc/gcleak.nim
@@ -2,6 +2,9 @@ discard """
   outputsub: "no leak: "
 """
 
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
 type
   TTestObj = object of TObject
     x: string
diff --git a/tests/gc/gcleak2.nim b/tests/gc/gcleak2.nim
index bd7962a7e..a50541fbc 100755
--- a/tests/gc/gcleak2.nim
+++ b/tests/gc/gcleak2.nim
@@ -2,6 +2,9 @@ discard """
   outputsub: "no leak: "
 """
 
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
 type
   TTestObj = object of TObject
     x: string
diff --git a/tests/gc/gcleak3.nim b/tests/gc/gcleak3.nim
index 70aeda3bb..588e238e9 100755
--- a/tests/gc/gcleak3.nim
+++ b/tests/gc/gcleak3.nim
@@ -2,6 +2,9 @@ discard """
   outputsub: "no leak: "
 """
 
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
 type
   TSomething = object
     s: string
diff --git a/tests/specials.nim b/tests/specials.nim
index 99e5c4c10..da2013e02 100644
--- a/tests/specials.nim
+++ b/tests/specials.nim
@@ -122,6 +122,8 @@ proc runGcTests(r: var TResults, options: string) =
   template test(filename: expr): stmt =
     runSingleTest(r, "tests/gc" / filename, options)
     runSingleTest(r, "tests/gc" / filename, options & " -d:release")
+    runSingleTest(r, "tests/gc" / filename, options &
+                  " -d:release -d:useRealtimeGC")
   
   test "gcbench"
   test "gcleak"
diff --git a/todo.txt b/todo.txt
index 5cd7fa2b0..83c97739b 100755
--- a/todo.txt
+++ b/todo.txt
@@ -1,7 +1,8 @@
 version 0.9.0
 =============
 
-- make GC realtime capable: GC_step(ms: int)
+- complete GC's documentation
+- make ``cookies`` part of the stdlib's documentation
 - make templates hygienic by default
 - ``=`` should be overloadable; requires specialization for ``=``
 - fix remaining generics bugs
diff --git a/web/index.txt b/web/index.txt
index 3fb645a5d..e9172c155 100755
--- a/web/index.txt
+++ b/web/index.txt
@@ -43,7 +43,7 @@ Nimrod is efficient
 * Native code generation (currently via compilation to C), not dependent on a
   virtual machine: **Nimrod produces small executables without dependencies
   for easy redistribution.**
-* A fast **non-tracing** garbage collector that should be well suited for soft 
+* A fast **non-tracing** garbage collector that supports soft 
   real-time systems (like games).
 * System programming features: Ability to manage your own memory and access the
   hardware directly. Pointers to garbage collected memory are distinguished
diff --git a/web/news.txt b/web/news.txt
index 642bd2c72..1a1c5fe3b 100755
--- a/web/news.txt
+++ b/web/news.txt
@@ -35,6 +35,8 @@ Library Additions
 - Added a wrapper for ``libsvm``.
 - Added a wrapper for ``mongodb``.
 - Added ``terminal.isatty``.
+- The GC supports (soft) realtime systems via ``GC_setMaxPause`` 
+  and ``GC_step`` procs.
 
 
 Changes affecting backwards compatibility
diff --git a/web/nimrod.ini b/web/nimrod.ini
index 68ba72936..6192c6f3d 100755
--- a/web/nimrod.ini
+++ b/web/nimrod.ini
@@ -23,7 +23,7 @@ file: ticker
 [Documentation]
 doc: "endb;intern;apis;lib;manual;tut1;tut2;nimrodc;overview;filters"
 doc: "tools;c2nim;niminst;nimgrep"
-pdf: "manual;lib;tut1;tut2;nimrodc;c2nim;niminst"
+pdf: "manual;lib;tut1;tut2;nimrodc;c2nim;niminst;gc"
 srcdoc: "core/macros;pure/marshal;core/typeinfo"
 srcdoc: "impure/graphics;impure/re;pure/sockets"
 srcdoc: "system.nim;system/threads.nim;system/channels.nim"