Skip to content

Commit b93bc12

Browse files
aviethRyanGlScott
authored andcommitted
performGC before getting RTS/GC stats (#177)
* performGC before getting RTS/GC stats The deprecated getGCStats had some nice documentation: "If you would like your statistics as recent as possible, first run a performGC" This is sadly missing from getRTSStats but I believe it still holds. When regressing allocated over iters, I'd see a believable slope, but an unbelievable y intercept, something like -300000 when the slope is 150 or so. That's because the first hundred or so measurements were 0 for bytes allocated, as the RTS didn't bother to run a GC before the RTS stats were sampled. Now we do 3 samples: 1. performGC before the first one, to ensure it's up-to-date. 2. Do the second one after the action, without a performGC, so we can get legit readings on the GC-related stats. 3. performGC and then sample again, so we can get up-to-date readings on other metrics. 4. Carefully choose whether to diff start stats against the end stats per- or post-GC. Also included is a fix to the ToJSON Measurements instance, which duplicated the mutator cpu seconds where GC cpu seconds should go. * fix shadowed binding warning * comment applyGCStatistics and simplify diff * update comment for getGCStatistics
1 parent ff5dc4e commit b93bc12

File tree

2 files changed

+29
-14
lines changed

2 files changed

+29
-14
lines changed

Criterion/Measurement.hs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ data GCStatistics = GCStatistics
108108
-- | Try to get GC statistics, bearing in mind that the GHC runtime
109109
-- will throw an exception if statistics collection was not enabled
110110
-- using \"@+RTS -T@\".
111+
-- If you need guaranteed up-to-date stats, call 'performGC' first.
111112
getGCStatistics :: IO (Maybe GCStatistics)
112113
#if MIN_VERSION_base(4,10,0)
113114
-- Use RTSStats/GCDetails to gather GC stats
@@ -170,16 +171,23 @@ measure :: Benchmarkable -- ^ Operation to benchmark.
170171
-> Int64 -- ^ Number of iterations.
171172
-> IO (Measured, Double)
172173
measure bm iters = runBenchmarkable bm iters addResults $ \ !n act -> do
174+
-- Ensure the stats from getGCStatistics are up-to-date.
175+
performGC
173176
startStats <- getGCStatistics
174177
startTime <- getTime
175178
startCpuTime <- getCPUTime
176179
startCycles <- getCycles
177180
act
178-
endStats <- getGCStatistics
179181
endTime <- getTime
180182
endCpuTime <- getCPUTime
181183
endCycles <- getCycles
182-
let !m = applyGCStatistics endStats startStats $ measured {
184+
-- From these we can derive GC-related deltas.
185+
endStatsPreGC <- getGCStatistics
186+
performGC
187+
-- From these we can derive all other deltas, and performGC guarantees they
188+
-- are up-to-date.
189+
endStatsPostGC <- getGCStatistics
190+
let !m = applyGCStatistics endStatsPostGC endStatsPreGC startStats $ measured {
183191
measTime = max 0 (endTime - startTime)
184192
, measCpuTime = max 0 (endCpuTime - startCpuTime)
185193
, measCycles = max 0 (fromIntegral (endCycles - startCycles))
@@ -313,22 +321,29 @@ measured = Measured {
313321
-- | Apply the difference between two sets of GC statistics to a
314322
-- measurement.
315323
applyGCStatistics :: Maybe GCStatistics
316-
-- ^ Statistics gathered at the __end__ of a run.
324+
-- ^ Statistics gathered at the __end__ of a run, post GC.
325+
-> Maybe GCStatistics
326+
-- ^ Statistics gathered at the __end__ of a run, pre GC.
317327
-> Maybe GCStatistics
318328
-- ^ Statistics gathered at the __beginning__ of a run.
319329
-> Measured
320330
-- ^ Value to \"modify\".
321331
-> Measured
322-
applyGCStatistics (Just end) (Just start) m = m {
323-
measAllocated = diff gcStatsBytesAllocated
324-
, measNumGcs = diff gcStatsNumGcs
325-
, measBytesCopied = diff gcStatsBytesCopied
326-
, measMutatorWallSeconds = diff gcStatsMutatorWallSeconds
327-
, measMutatorCpuSeconds = diff gcStatsMutatorCpuSeconds
328-
, measGcWallSeconds = diff gcStatsGcWallSeconds
329-
, measGcCpuSeconds = diff gcStatsGcCpuSeconds
330-
} where diff f = f end - f start
331-
applyGCStatistics _ _ m = m
332+
applyGCStatistics (Just endPostGC) (Just endPreGC) (Just start) m = m {
333+
-- The choice of endPostGC or endPreGC is important.
334+
-- For bytes allocated/copied, and mutator statistics, we use
335+
-- endPostGC, because the intermediate performGC ensures they're up-to-date.
336+
-- The others (num GCs and GC cpu/wall seconds) must be diffed against
337+
-- endPreGC so that the extra performGC does not taint them.
338+
measAllocated = diff endPostGC gcStatsBytesAllocated
339+
, measNumGcs = diff endPreGC gcStatsNumGcs
340+
, measBytesCopied = diff endPostGC gcStatsBytesCopied
341+
, measMutatorWallSeconds = diff endPostGC gcStatsMutatorWallSeconds
342+
, measMutatorCpuSeconds = diff endPostGC gcStatsMutatorCpuSeconds
343+
, measGcWallSeconds = diff endPreGC gcStatsGcWallSeconds
344+
, measGcCpuSeconds = diff endPreGC gcStatsGcCpuSeconds
345+
} where diff a f = f a - f start
346+
applyGCStatistics _ _ _ m = m
332347

333348
-- | Convert a number of seconds to a string. The string will consist
334349
-- of four decimal places, followed by a short description of the time

Criterion/Types.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ instance ToJSON Measured where
212212
(measTime, measCpuTime, measCycles, measIters,
213213
i measAllocated, i measNumGcs, i measBytesCopied,
214214
d measMutatorWallSeconds, d measMutatorCpuSeconds,
215-
d measGcWallSeconds, d measMutatorCpuSeconds)
215+
d measGcWallSeconds, d measGcCpuSeconds)
216216
where i = fromInt; d = fromDouble
217217

218218
instance NFData Measured where

0 commit comments

Comments
 (0)