Use __rdtsc on Windows.
authorMike Klein <mtklein@google.com>
Wed, 16 Jul 2014 23:59:32 +0000 (19:59 -0400)
committerMike Klein <mtklein@google.com>
Wed, 16 Jul 2014 23:59:32 +0000 (19:59 -0400)
This seems to be ~100x higher resolution than QueryPerformanceCounter.  AFAIK, all our Windows perf bots have constant_tsc, so we can be a bit more direct about using rdtsc directly: it'll always tick at the max CPU frequency.

Now, the question remains, what is the max CPU frequency to divide through by?  It looks like QueryPerformanceFrequency actually gives the CPU frequency in kHz, suspiciously exactly what we need to divide through to get elapsed milliseconds.  That was a freebie.

I did some before/after comparison on slow benchmarks.  Timings look the same.  Going to land this without review tonight to see what happens on the bots; happy to review carefully tomorrow.

R=mtklein@google.com
TBR=bungeman

BUG=skia:

Review URL: https://codereview.chromium.org/394363003

bench/nanobench.cpp
tools/Stats.h
tools/timer/SysTimer_windows.cpp
tools/timer/SysTimer_windows.h

index 4453707..a9862c6 100644 (file)
@@ -50,8 +50,6 @@ DEFINE_bool(gpu, true, "Master switch for GPU-bound work.");
 
 DEFINE_string(outResultsFile, "", "If given, write results here as JSON.");
 DEFINE_bool(resetGpuContext, true, "Reset the GrContext before running each bench.");
-DEFINE_int32(maxCalibrationAttempts, 3,
-             "Try up to this many times to guess loops for a bench, or skip the bench.");
 
 
 static SkString humanize(double ms) {
@@ -95,13 +93,8 @@ static double estimate_timer_overhead() {
 static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) {
     // First figure out approximately how many loops of bench it takes to make overhead negligible.
     double bench_plus_overhead;
-    int round = 0;
     do {
         bench_plus_overhead = time(1, bench, canvas, NULL);
-        if (++round == FLAGS_maxCalibrationAttempts) {
-            // At some point we have to just give up.
-            return 0;
-        }
     } while (bench_plus_overhead < overhead);
 
     // Later we'll just start and stop the timer once but loop N times.
@@ -288,6 +281,8 @@ int tool_main(int argc, char** argv) {
     fill_static_options(&log);
 
     const double overhead = estimate_timer_overhead();
+    SkDebugf("Timer overhead: %s\n", humanize(overhead).c_str());
+
     SkAutoTMalloc<double> samples(FLAGS_samples);
 
     if (FLAGS_runOnce) {
index 4fddc9b..8487a94 100644 (file)
@@ -1,8 +1,6 @@
 #ifndef Stats_DEFINED
 #define Stats_DEFINED
 
-#include <math.h>
-
 #include "SkString.h"
 #include "SkTSort.h"
 
@@ -50,7 +48,7 @@ struct Stats {
             s -= min;
             s /= (max - min);
             s *= (SK_ARRAY_COUNT(kBars) - 1);
-            const size_t bar = (size_t)round(s);
+            const size_t bar = (size_t)(s + 0.5);
             SK_ALWAYSBREAK(bar < SK_ARRAY_COUNT(kBars));
             plot.append(kBars[bar]);
         }
index 2f9d0a5..8e45b4a 100644 (file)
@@ -6,6 +6,8 @@
  */
 #include "SysTimer_windows.h"
 
+#include <intrin.h>
+
 static ULONGLONG win_cpu_time() {
     FILETIME createTime;
     FILETIME exitTime;
@@ -23,11 +25,6 @@ static ULONGLONG win_cpu_time() {
     return start_cpu_sys.QuadPart + start_cpu_usr.QuadPart;
 }
 
-void SysTimer::startWall() {
-    if (0 == ::QueryPerformanceCounter(&fStartWall)) {
-        fStartWall.QuadPart = 0;
-    }
-}
 void SysTimer::startCpu() {
     fStartCpu = win_cpu_time();
 }
@@ -36,21 +33,21 @@ double SysTimer::endCpu() {
     ULONGLONG end_cpu = win_cpu_time();
     return static_cast<double>(end_cpu - fStartCpu) / 10000.0L;
 }
+
+// On recent Intel chips (roughly, "has Core or Atom in its name") __rdtsc will always tick
+// at the CPU's maximum rate, even while power management clocks the CPU up and down.
+// That's great, because it makes measuring wall time super simple.
+
+void SysTimer::startWall() {
+    fStartWall = __rdtsc();
+}
+
 double SysTimer::endWall() {
-    LARGE_INTEGER end_wall;
-    if (0 == ::QueryPerformanceCounter(&end_wall)) {
-        end_wall.QuadPart = 0;
-    }
+    unsigned __int64 end = __rdtsc();
 
-    LARGE_INTEGER ticks_elapsed;
-    ticks_elapsed.QuadPart = end_wall.QuadPart - fStartWall.QuadPart;
+    // This seems to, weirdly, give the CPU frequency in kHz.  That's exactly what we want!
+    LARGE_INTEGER freq_khz;
+    QueryPerformanceFrequency(&freq_khz);
 
-    LARGE_INTEGER frequency;
-    if (0 == ::QueryPerformanceFrequency(&frequency)) {
-        return 0.0L;
-    } else {
-        return static_cast<double>(ticks_elapsed.QuadPart)
-             / static_cast<double>(frequency.QuadPart)
-             * 1000.0L;
-    }
+    return static_cast<double>(end - fStartWall) / static_cast<double>(freq_khz.QuadPart);
 }
index 62a9445..380debd 100644 (file)
@@ -19,7 +19,7 @@ public:
     double endWall();
 private:
     ULONGLONG fStartCpu;
-    LARGE_INTEGER fStartWall;
+    unsigned __int64 fStartWall;
 };
 
 #endif