diff options
-rw-r--r-- | docs/users_guide/runtime_control.rst | 38 | ||||
-rw-r--r-- | includes/RtsAPI.h | 4 | ||||
-rw-r--r-- | includes/rts/Flags.h | 2 | ||||
-rw-r--r-- | rts/RtsFlags.c | 15 | ||||
-rw-r--r-- | rts/hooks/Hooks.h | 2 | ||||
-rw-r--r-- | rts/hooks/LongGCSync.c | 41 | ||||
-rw-r--r-- | rts/sm/GC.c | 22 | ||||
-rw-r--r-- | rts/sm/GCThread.h | 6 |
8 files changed, 124 insertions, 6 deletions
diff --git a/docs/users_guide/runtime_control.rst b/docs/users_guide/runtime_control.rst index f944ae95fe..f8e0ce026c 100644 --- a/docs/users_guide/runtime_control.rst +++ b/docs/users_guide/runtime_control.rst @@ -728,6 +728,44 @@ performance. that indicates the NUMA nodes on which to run the program. For example, ``--numa=3`` would run the program on NUMA nodes 0 and 1. +.. rts-flag:: --long-gc-sync + --long-gc-sync=<seconds> + + .. index:: + single: GC sync time, measuring + + When a GC starts, all the running mutator threads have to stop and + synchronise. The period between when the GC is initiated and all + the mutator threads are stopped is called the GC synchronisation + phase. If this phase is taking a long time (longer than 1ms is + considered long), then it can have a severe impact on overall + throughput. + + A long GC sync can be caused by a mutator thread that is inside an + ``unsafe`` FFI call, or running in a loop that doesn't allocate + memory and so doesn't yield. To fix the former, make the call + ``safe``, and to fix the latter, either avoid calling the code in + question or compile it with :ghc-flag:`-fomit-yields`. + + By default, the flag will cause a warning to be emitted to stderr + when the sync time exceeds the specified time. This behaviour can + be overriden, however: the ``longGCSync()`` hook is called when + the sync time is exceeded during the sync period, and the + ``longGCSyncEnd()`` hook at the end. Both of these hooks can be + overriden in the ``RtsConfig`` when the runtime is started with + ``hs_init_ghc()``. The default implementations of these hooks + (``LongGcSync()`` and ``LongGCSyncEnd()`` respectively) print + warnings to stderr. + + One way to use this flag is to set a breakpoint on + ``LongGCSync()`` in the debugger, and find the thread that is + delaying the sync. You probably want to use :ghc-flag:`-g` to + provide more info to the debugger. + + The GC sync time, along with other GC stats, are available by + calling the ``getRTSStats()`` function from C, or + ``GHC.Stats.getRTSStats`` from Haskell. + .. _rts-options-statistics: RTS options to produce runtime statistics diff --git a/includes/RtsAPI.h b/includes/RtsAPI.h index ca61328b7c..27a5080220 100644 --- a/includes/RtsAPI.h +++ b/includes/RtsAPI.h @@ -102,6 +102,10 @@ typedef struct { // Called for every GC void (* gcDoneHook) (const struct GCDetails_ *stats); + + // Called when GC sync takes too long (+RTS --long-gc-sync=<time>) + void (* longGCSync) (uint32_t this_cap, Time time_ns); + void (* longGCSyncEnd) (Time time_ns); } RtsConfig; // Clients should start with defaultRtsConfig and then customise it. diff --git a/includes/rts/Flags.h b/includes/rts/Flags.h index ff45bd378f..aed4dca384 100644 --- a/includes/rts/Flags.h +++ b/includes/rts/Flags.h @@ -65,6 +65,8 @@ typedef struct _GC_FLAGS { Time idleGCDelayTime; /* units: TIME_RESOLUTION */ bool doIdleGC; + Time longGCSync; /* units: TIME_RESOLUTION */ + StgWord heapBase; /* address to ask the OS for memory */ StgWord allocLimitGrace; /* units: *blocks* diff --git a/rts/RtsFlags.c b/rts/RtsFlags.c index c0571368cd..ecb9228234 100644 --- a/rts/RtsFlags.c +++ b/rts/RtsFlags.c @@ -69,7 +69,9 @@ const RtsConfig defaultRtsConfig = { .stackOverflowHook = StackOverflowHook, .outOfHeapHook = OutOfHeapHook, .mallocFailHook = MallocFailHook, - .gcDoneHook = NULL + .gcDoneHook = NULL, + .longGCSync = LongGCSync, + .longGCSyncEnd = LongGCSyncEnd }; /* @@ -165,6 +167,7 @@ void initRtsFlagsDefaults(void) RtsFlags.GcFlags.numa = false; RtsFlags.GcFlags.numaMask = 1; RtsFlags.GcFlags.ringBell = false; + RtsFlags.GcFlags.longGCSync = 0; /* detection turned off */ RtsFlags.DebugFlags.scheduler = false; RtsFlags.DebugFlags.interpreter = false; @@ -937,6 +940,16 @@ error = true; } } #endif + else if (!strncmp("long-gc-sync=", &rts_argv[arg][2], 13)) { + OPTION_SAFE; + if (rts_argv[arg][2] == '\0') { + /* use default */ + } else { + RtsFlags.GcFlags.longGCSync = + fsecondsToTime(atof(rts_argv[arg]+16)); + } + break; + } else { OPTION_SAFE; errorBelch("unknown RTS option: %s",rts_argv[arg]); diff --git a/rts/hooks/Hooks.h b/rts/hooks/Hooks.h index 760e1daefc..24500f19e7 100644 --- a/rts/hooks/Hooks.h +++ b/rts/hooks/Hooks.h @@ -22,5 +22,7 @@ extern void StackOverflowHook (W_ stack_size); extern void OutOfHeapHook (W_ request_size, W_ heap_size); extern void MallocFailHook (W_ request_size /* in bytes */, const char *msg); extern void FlagDefaultsHook (void); +extern void LongGCSync (uint32_t capno, Time t); +extern void LongGCSyncEnd (Time t); #include "EndPrivate.h" diff --git a/rts/hooks/LongGCSync.c b/rts/hooks/LongGCSync.c new file mode 100644 index 0000000000..351406df98 --- /dev/null +++ b/rts/hooks/LongGCSync.c @@ -0,0 +1,41 @@ +/* ----------------------------------------------------------------------------- + * + * User-overridable RTS hooks. + * + * ---------------------------------------------------------------------------*/ + +#include "PosixSource.h" +#include "Rts.h" +#include "sm/GC.h" +#include "sm/GCThread.h" +#include "Hooks.h" + +/* + * Called when --long-gc-sync=<time> has expired during a GC sync. The idea is + * that you can set a breakpoint on this function in gdb and try to determine + * which thread was holding up the GC sync. + */ +void LongGCSync (uint32_t me USED_IF_THREADS, Time t STG_UNUSED) +{ +#if defined(THREADED_RTS) + { + uint32_t i; + for (i=0; i < n_capabilities; i++) { + if (i != me && gc_threads[i]->wakeup != GC_THREAD_STANDING_BY) { + debugBelch("Warning: slow GC sync: still waiting for cap %d\n", + i); + } + } + } +#endif +} + +/* + * Called at the end of a GC sync which was longer than --long-gc-sync=<time>. + * The idea is that you can use this function to log stats about the length of + * GC syncs. + */ +void LongGCSyncEnd (Time t) +{ + debugBelch("Warning: waited %" FMT_Word64 "us for GC sync\n", TimeToUS(t)); +} diff --git a/rts/sm/GC.c b/rts/sm/GC.c index aa804a8b76..4dbc5e0aa6 100644 --- a/rts/sm/GC.c +++ b/rts/sm/GC.c @@ -48,6 +48,7 @@ #include "Stable.h" #include "CheckUnload.h" #include "CNF.h" +#include "RtsFlags.h" #include <string.h> // for memset() #include <unistd.h> @@ -825,11 +826,6 @@ static void heapOverflow(void) Initialise the gc_thread structures. -------------------------------------------------------------------------- */ -#define GC_THREAD_INACTIVE 0 -#define GC_THREAD_STANDING_BY 1 -#define GC_THREAD_RUNNING 2 -#define GC_THREAD_WAITING_TO_CONTINUE 3 - static void new_gc_thread (uint32_t n, gc_thread *t) { @@ -1132,6 +1128,9 @@ waitForGcThreads (Capability *cap USED_IF_THREADS, bool idle_cap[]) const uint32_t me = cap->no; uint32_t i, j; bool retry = true; + Time t0, t1, t2; + + t0 = t1 = t2 = getProcessElapsedTime(); while(retry) { for (i=0; i < n_threads; i++) { @@ -1153,6 +1152,19 @@ waitForGcThreads (Capability *cap USED_IF_THREADS, bool idle_cap[]) if (!retry) break; yieldThread(); } + + t2 = getProcessElapsedTime(); + if (RtsFlags.GcFlags.longGCSync != 0 && + t2 - t1 > RtsFlags.GcFlags.longGCSync) { + /* call this every longGCSync of delay */ + rtsConfig.longGCSync(cap->no, t2 - t0); + t1 = t2; + } + } + + if (RtsFlags.GcFlags.longGCSync != 0 && + t2 - t0 > RtsFlags.GcFlags.longGCSync) { + rtsConfig.longGCSyncEnd(t2 - t0); } } diff --git a/rts/sm/GCThread.h b/rts/sm/GCThread.h index bb206db64c..e865dabe5d 100644 --- a/rts/sm/GCThread.h +++ b/rts/sm/GCThread.h @@ -116,6 +116,12 @@ typedef struct gen_workspace_ { of the GC threads ------------------------------------------------------------------------- */ +/* values for the wakeup field */ +#define GC_THREAD_INACTIVE 0 +#define GC_THREAD_STANDING_BY 1 +#define GC_THREAD_RUNNING 2 +#define GC_THREAD_WAITING_TO_CONTINUE 3 + typedef struct gc_thread_ { Capability *cap; |