summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruce Dawson <brucedawson@chromium.org>2021-08-27 16:38:21 -0700
committerBruce Dawson <brucedawson@chromium.org>2021-08-27 16:38:21 -0700
commit72f191288dfe29b70923b41a71bb41f610cb9437 (patch)
tree2143622234e78608843e3fbaf433dc0ace91bea3 /src
parente90dfd3c7528b9c620eab29121a3591af7bf035e (diff)
downloadninja-72f191288dfe29b70923b41a71bb41f610cb9437.tar.gz
Make ScopedMetric portable, accurate, and efficient
ScopedMetric used to use separate code paths for Win32 and other platforms. std::chrono makes it practical to use the same code everywhere. This requires C++ 11 so that is specified in the configuration file (C++ 11 was already used on Win32 builds). This change also makes ScopedMetric more accurate because it postpones converting the raw deltas until it is time to report them. Previously they would be converted at each measurement site, potentially losing almost a microsecond each time. Postponing the conversion also reduces the cost of ScopedMetric. The performance of std::chrono is currently harmed a bit by some design and code-gen issues in VC++, but bugs have been filed and these should be fixed. The overhead is low enough to be inconsequential for the O(15,000) ScopedMetric objects created in a build of Chrome. The net result is that the ScopedMetric code is now portable, more accurate, and as fast or faster than it used to be. This will resolve issue #2004.
Diffstat (limited to 'src')
-rw-r--r--src/metrics.cc66
-rw-r--r--src/metrics.h12
2 files changed, 33 insertions, 45 deletions
diff --git a/src/metrics.cc b/src/metrics.cc
index dbaf221..caad696 100644
--- a/src/metrics.cc
+++ b/src/metrics.cc
@@ -18,13 +18,8 @@
#include <stdio.h>
#include <string.h>
-#ifndef _WIN32
-#include <sys/time.h>
-#else
-#include <windows.h>
-#endif
-
#include <algorithm>
+#include <chrono>
#include "util.h"
@@ -34,45 +29,30 @@ Metrics* g_metrics = NULL;
namespace {
-#ifndef _WIN32
/// Compute a platform-specific high-res timer value that fits into an int64.
int64_t HighResTimer() {
- timeval tv;
- if (gettimeofday(&tv, NULL) < 0)
- Fatal("gettimeofday: %s", strerror(errno));
- return (int64_t)tv.tv_sec * 1000*1000 + tv.tv_usec;
+ auto now = chrono::steady_clock::now();
+ return chrono::duration_cast<chrono::steady_clock::duration>(now.time_since_epoch()).count();
}
-/// Convert a delta of HighResTimer() values to microseconds.
-int64_t TimerToMicros(int64_t dt) {
- // No conversion necessary.
- return dt;
-}
-#else
-int64_t LargeIntegerToInt64(const LARGE_INTEGER& i) {
- return ((int64_t)i.HighPart) << 32 | i.LowPart;
-}
+constexpr int64_t GetFrequency() {
+ constexpr auto den = std::chrono::steady_clock::period::den;
+ constexpr auto num = std::chrono::steady_clock::period::num;
-int64_t HighResTimer() {
- LARGE_INTEGER counter;
- if (!QueryPerformanceCounter(&counter))
- Fatal("QueryPerformanceCounter: %s", GetLastErrorString().c_str());
- return LargeIntegerToInt64(counter);
+ // If numerator isn't 1 then we lose precision and that will need to be assessed.
+ static_assert(num == 1, "Numerator must be 1");
+ return den / num;
}
int64_t TimerToMicros(int64_t dt) {
- static int64_t ticks_per_sec = 0;
- if (!ticks_per_sec) {
- LARGE_INTEGER freq;
- if (!QueryPerformanceFrequency(&freq))
- Fatal("QueryPerformanceFrequency: %s", GetLastErrorString().c_str());
- ticks_per_sec = LargeIntegerToInt64(freq);
- }
+ // dt is in ticks. We want microseconds.
+ return (dt * 1000000) / GetFrequency();
+}
+int64_t TimerToMicros(double dt) {
// dt is in ticks. We want microseconds.
- return (dt * 1000000) / ticks_per_sec;
+ return (dt * 1000000) / GetFrequency();
}
-#endif
} // anonymous namespace
@@ -87,7 +67,9 @@ ScopedMetric::~ScopedMetric() {
if (!metric_)
return;
metric_->count++;
- int64_t dt = TimerToMicros(HighResTimer() - start_);
+ // Leave in the timer's natural frequency to avoid paying the conversion cost on
+ // every measurement.
+ int64_t dt = HighResTimer() - start_;
metric_->sum += dt;
}
@@ -112,15 +94,21 @@ void Metrics::Report() {
for (vector<Metric*>::iterator i = metrics_.begin();
i != metrics_.end(); ++i) {
Metric* metric = *i;
- double total = metric->sum / (double)1000;
- double avg = metric->sum / (double)metric->count;
+ uint64_t micros = TimerToMicros(metric->sum);
+ double total = micros / (double)1000;
+ double avg = micros / (double)metric->count;
printf("%-*s\t%-6d\t%-8.1f\t%.1f\n", width, metric->name.c_str(),
metric->count, avg, total);
}
}
-uint64_t Stopwatch::Now() const {
- return TimerToMicros(HighResTimer());
+double Stopwatch::Elapsed() const {
+ // Convert to micros after converting to double to minimize error.
+ return 1e-6 * TimerToMicros(static_cast<double>(NowRaw() - started_));
+}
+
+uint64_t Stopwatch::NowRaw() const {
+ return HighResTimer();
}
int64_t GetTimeMillis() {
diff --git a/src/metrics.h b/src/metrics.h
index 11239b5..549e935 100644
--- a/src/metrics.h
+++ b/src/metrics.h
@@ -28,7 +28,7 @@ struct Metric {
std::string name;
/// Number of times we've hit the code path.
int count;
- /// Total time (in micros) we've spent on the code path.
+ /// Total time (in platform-dependent units) we've spent on the code path.
int64_t sum;
};
@@ -68,15 +68,15 @@ struct Stopwatch {
Stopwatch() : started_(0) {}
/// Seconds since Restart() call.
- double Elapsed() const {
- return 1e-6 * static_cast<double>(Now() - started_);
- }
+ double Elapsed() const;
- void Restart() { started_ = Now(); }
+ void Restart() { started_ = NowRaw(); }
private:
uint64_t started_;
- uint64_t Now() const;
+ // Return the current time using the native frequency of the high resolution
+ // timer.
+ uint64_t NowRaw() const;
};
/// The primary interface to metrics. Use METRIC_RECORD("foobar") at the top