Commit e0b75e77 authored by Christopher Dykes's avatar Christopher Dykes Committed by Facebook Github Bot

Use std::chrono::high_resolution_clock for folly::Benchmark

Summary: `clock_gettime(CLOCK_REALTIME)` is based on `std::system_clock` on Windows, but that only has a resolution that's the same as `FILETIME` (100s of nanoseconds), so modernize things and use `std::chrono::high_resolution_clock` which is intended for this purpose.

Reviewed By: yfeldblum

Differential Revision: D4476671

fbshipit-source-id: 3db1debc8f408f689b5c9fe1966a72b8dad4da93
parent b6916a3a
...@@ -62,7 +62,7 @@ DEFINE_int32( ...@@ -62,7 +62,7 @@ DEFINE_int32(
namespace folly { namespace folly {
BenchmarkSuspender::NanosecondsSpent BenchmarkSuspender::nsSpent; std::chrono::high_resolution_clock::duration BenchmarkSuspender::timeSpent;
typedef function<detail::TimeIterPair(unsigned int)> BenchmarkFun; typedef function<detail::TimeIterPair(unsigned int)> BenchmarkFun;
...@@ -118,33 +118,31 @@ static double estimateTime(double * begin, double * end) { ...@@ -118,33 +118,31 @@ static double estimateTime(double * begin, double * end) {
static double runBenchmarkGetNSPerIteration(const BenchmarkFun& fun, static double runBenchmarkGetNSPerIteration(const BenchmarkFun& fun,
const double globalBaseline) { const double globalBaseline) {
using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
using std::chrono::microseconds;
using std::chrono::nanoseconds;
using std::chrono::seconds;
// They key here is accuracy; too low numbers means the accuracy was // They key here is accuracy; too low numbers means the accuracy was
// coarse. We up the ante until we get to at least minNanoseconds // coarse. We up the ante until we get to at least minNanoseconds
// timings. // timings.
static uint64_t resolutionInNs = 0; static_assert(
if (!resolutionInNs) { std::is_same<high_resolution_clock::duration, nanoseconds>::value,
timespec ts; "High resolution clock must be nanosecond resolution.");
CHECK_EQ(0, clock_getres(CLOCK_REALTIME, &ts));
CHECK_EQ(0, ts.tv_sec) << "Clock sucks.";
CHECK_LT(0, ts.tv_nsec) << "Clock too fast for its own good.";
CHECK_EQ(1, ts.tv_nsec) << "Clock too coarse, upgrade your kernel.";
resolutionInNs = uint64_t(ts.tv_nsec);
}
// We choose a minimum minimum (sic) of 100,000 nanoseconds, but if // We choose a minimum minimum (sic) of 100,000 nanoseconds, but if
// the clock resolution is worse than that, it will be larger. In // the clock resolution is worse than that, it will be larger. In
// essence we're aiming at making the quantization noise 0.01%. // essence we're aiming at making the quantization noise 0.01%.
static const auto minNanoseconds = max<uint64_t>( static const auto minNanoseconds = std::max<nanoseconds>(
uint64_t(FLAGS_bm_min_usec) * 1000ULL, nanoseconds(100000), microseconds(FLAGS_bm_min_usec));
min<uint64_t>(resolutionInNs * 100000ULL, 1000000000ULL));
// We do measurements in several epochs and take the minimum, to // We do measurements in several epochs and take the minimum, to
// account for jitter. // account for jitter.
static const unsigned int epochs = 1000; static const unsigned int epochs = 1000;
// We establish a total time budget as we don't want a measurement // We establish a total time budget as we don't want a measurement
// to take too long. This will curtail the number of actual epochs. // to take too long. This will curtail the number of actual epochs.
const uint64_t timeBudgetInNs = FLAGS_bm_max_secs * 1000000000ULL; const auto timeBudget = seconds(FLAGS_bm_max_secs);
timespec global; auto global = high_resolution_clock::now();
CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &global));
double epochResults[epochs] = { 0 }; double epochResults[epochs] = { 0 };
size_t actualEpochs = 0; size_t actualEpochs = 0;
...@@ -158,14 +156,14 @@ static double runBenchmarkGetNSPerIteration(const BenchmarkFun& fun, ...@@ -158,14 +156,14 @@ static double runBenchmarkGetNSPerIteration(const BenchmarkFun& fun,
} }
// We got an accurate enough timing, done. But only save if // We got an accurate enough timing, done. But only save if
// smaller than the current result. // smaller than the current result.
epochResults[actualEpochs] = max(0.0, double(nsecsAndIter.first) / auto nsecs = duration_cast<nanoseconds>(nsecsAndIter.first).count();
nsecsAndIter.second - globalBaseline); epochResults[actualEpochs] =
max(0.0, double(nsecs) / nsecsAndIter.second - globalBaseline);
// Done with the current epoch, we got a meaningful timing. // Done with the current epoch, we got a meaningful timing.
break; break;
} }
timespec now; auto now = high_resolution_clock::now();
CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &now)); if (now - global >= timeBudget) {
if (detail::timespecDiff(now, global) >= timeBudgetInNs) {
// No more time budget available. // No more time budget available.
++actualEpochs; ++actualEpochs;
break; break;
......
...@@ -21,16 +21,16 @@ ...@@ -21,16 +21,16 @@
#include <folly/ScopeGuard.h> #include <folly/ScopeGuard.h>
#include <folly/Traits.h> #include <folly/Traits.h>
#include <folly/portability/GFlags.h> #include <folly/portability/GFlags.h>
#include <folly/portability/Time.h>
#include <cassert> #include <cassert>
#include <ctime> #include <chrono>
#include <boost/function_types/function_arity.hpp>
#include <functional> #include <functional>
#include <glog/logging.h>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <boost/function_types/function_arity.hpp>
#include <glog/logging.h>
DECLARE_bool(benchmark); DECLARE_bool(benchmark);
namespace folly { namespace folly {
...@@ -53,7 +53,8 @@ inline bool runBenchmarksOnFlag() { ...@@ -53,7 +53,8 @@ inline bool runBenchmarksOnFlag() {
namespace detail { namespace detail {
typedef std::pair<uint64_t, unsigned int> TimeIterPair; using TimeIterPair =
std::pair<std::chrono::high_resolution_clock::duration, unsigned int>;
/** /**
* Adds a benchmark wrapped in a std::function. Only used * Adds a benchmark wrapped in a std::function. Only used
...@@ -63,85 +64,51 @@ void addBenchmarkImpl(const char* file, ...@@ -63,85 +64,51 @@ void addBenchmarkImpl(const char* file,
const char* name, const char* name,
std::function<TimeIterPair(unsigned int)>); std::function<TimeIterPair(unsigned int)>);
/**
* Takes the difference between two timespec values. end is assumed to
* occur after start.
*/
inline uint64_t timespecDiff(timespec end, timespec start) {
if (end.tv_sec == start.tv_sec) {
assert(end.tv_nsec >= start.tv_nsec);
return uint64_t(end.tv_nsec - start.tv_nsec);
}
assert(end.tv_sec > start.tv_sec);
auto diff = uint64_t(end.tv_sec - start.tv_sec);
assert(diff < std::numeric_limits<uint64_t>::max() / 1000000000ULL);
return diff * 1000000000ULL + end.tv_nsec - start.tv_nsec;
}
/**
* Takes the difference between two sets of timespec values. The first
* two come from a high-resolution clock whereas the other two come
* from a low-resolution clock. The crux of the matter is that
* high-res values may be bogus as documented in
* http://linux.die.net/man/3/clock_gettime. The trouble is when the
* running process migrates from one CPU to another, which is more
* likely for long-running processes. Therefore we watch for high
* differences between the two timings.
*
* This function is subject to further improvements.
*/
inline uint64_t timespecDiff(timespec end, timespec start,
timespec endCoarse, timespec startCoarse) {
auto fine = timespecDiff(end, start);
auto coarse = timespecDiff(endCoarse, startCoarse);
if (coarse - fine >= 1000000) {
// The fine time is in all likelihood bogus
return coarse;
}
return fine;
}
} // namespace detail } // namespace detail
/** /**
* Supporting type for BENCHMARK_SUSPEND defined below. * Supporting type for BENCHMARK_SUSPEND defined below.
*/ */
struct BenchmarkSuspender { struct BenchmarkSuspender {
using Clock = std::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
BenchmarkSuspender() { BenchmarkSuspender() {
CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &start)); start = Clock::now();
} }
BenchmarkSuspender(const BenchmarkSuspender &) = delete; BenchmarkSuspender(const BenchmarkSuspender &) = delete;
BenchmarkSuspender(BenchmarkSuspender && rhs) noexcept { BenchmarkSuspender(BenchmarkSuspender && rhs) noexcept {
start = rhs.start; start = rhs.start;
rhs.start = {0, 0}; rhs.start = {};
} }
BenchmarkSuspender& operator=(const BenchmarkSuspender &) = delete; BenchmarkSuspender& operator=(const BenchmarkSuspender &) = delete;
BenchmarkSuspender& operator=(BenchmarkSuspender && rhs) { BenchmarkSuspender& operator=(BenchmarkSuspender && rhs) {
if (start.tv_nsec > 0 || start.tv_sec > 0) { if (start != TimePoint{}) {
tally(); tally();
} }
start = rhs.start; start = rhs.start;
rhs.start = {0, 0}; rhs.start = {};
return *this; return *this;
} }
~BenchmarkSuspender() { ~BenchmarkSuspender() {
if (start.tv_nsec > 0 || start.tv_sec > 0) { if (start != TimePoint{}) {
tally(); tally();
} }
} }
void dismiss() { void dismiss() {
assert(start.tv_nsec > 0 || start.tv_sec > 0); assert(start != TimePoint{});
tally(); tally();
start = {0, 0}; start = {};
} }
void rehire() { void rehire() {
assert(start.tv_nsec == 0 || start.tv_sec == 0); assert(start == TimePoint{});
CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &start)); start = Clock::now();
} }
template <class F> template <class F>
...@@ -160,20 +127,18 @@ struct BenchmarkSuspender { ...@@ -160,20 +127,18 @@ struct BenchmarkSuspender {
} }
/** /**
* Accumulates nanoseconds spent outside benchmark. * Accumulates time spent outside benchmark.
*/ */
typedef uint64_t NanosecondsSpent; static Duration timeSpent;
static NanosecondsSpent nsSpent;
private: private:
void tally() { void tally() {
timespec end; auto end = Clock::now();
CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &end)); timeSpent += end - start;
nsSpent += detail::timespecDiff(end, start);
start = end; start = end;
} }
timespec start; TimePoint start;
}; };
/** /**
...@@ -190,22 +155,17 @@ typename std::enable_if< ...@@ -190,22 +155,17 @@ typename std::enable_if<
>::type >::type
addBenchmark(const char* file, const char* name, Lambda&& lambda) { addBenchmark(const char* file, const char* name, Lambda&& lambda) {
auto execute = [=](unsigned int times) { auto execute = [=](unsigned int times) {
BenchmarkSuspender::nsSpent = 0; BenchmarkSuspender::timeSpent = {};
timespec start, end;
unsigned int niter; unsigned int niter;
// CORE MEASUREMENT STARTS // CORE MEASUREMENT STARTS
auto const r1 = clock_gettime(CLOCK_REALTIME, &start); auto start = std::chrono::high_resolution_clock::now();
niter = lambda(times); niter = lambda(times);
auto const r2 = clock_gettime(CLOCK_REALTIME, &end); auto end = std::chrono::high_resolution_clock::now();
// CORE MEASUREMENT ENDS // CORE MEASUREMENT ENDS
CHECK_EQ(0, r1);
CHECK_EQ(0, r2);
return detail::TimeIterPair( return detail::TimeIterPair(
detail::timespecDiff(end, start) - BenchmarkSuspender::nsSpent, (end - start) - BenchmarkSuspender::timeSpent, niter);
niter);
}; };
detail::addBenchmarkImpl(file, name, detail::addBenchmarkImpl(file, name,
......
...@@ -97,6 +97,17 @@ struct LatencyTest { ...@@ -97,6 +97,17 @@ struct LatencyTest {
computeTimeCost(); computeTimeCost();
} }
static uint64_t timespecDiff(timespec end, timespec start) {
if (end.tv_sec == start.tv_sec) {
assert(end.tv_nsec >= start.tv_nsec);
return uint64_t(end.tv_nsec - start.tv_nsec);
}
assert(end.tv_sec > start.tv_sec);
auto diff = uint64_t(end.tv_sec - start.tv_sec);
assert(diff < std::numeric_limits<uint64_t>::max() / 1000000000ULL);
return diff * 1000000000ULL + end.tv_nsec - start.tv_nsec;
}
void computeTimeCost() { void computeTimeCost() {
timespec start, end; timespec start, end;
clock_gettime(CLOCK_REALTIME, &start); clock_gettime(CLOCK_REALTIME, &start);
...@@ -105,7 +116,7 @@ struct LatencyTest { ...@@ -105,7 +116,7 @@ struct LatencyTest {
clock_gettime(CLOCK_REALTIME, &tv); clock_gettime(CLOCK_REALTIME, &tv);
} }
clock_gettime(CLOCK_REALTIME, &end); clock_gettime(CLOCK_REALTIME, &end);
time_cost_ = 2 * detail::timespecDiff(end, start) / iters_; time_cost_ = 2 * timespecDiff(end, start) / iters_;
} }
void producer() { void producer() {
...@@ -120,7 +131,7 @@ struct LatencyTest { ...@@ -120,7 +131,7 @@ struct LatencyTest {
clock_gettime(CLOCK_REALTIME, &sleepstart); clock_gettime(CLOCK_REALTIME, &sleepstart);
do { do {
clock_gettime(CLOCK_REALTIME, &sleeptime); clock_gettime(CLOCK_REALTIME, &sleeptime);
} while (detail::timespecDiff(sleeptime, sleepstart) < 1000000); } while (timespecDiff(sleeptime, sleepstart) < 1000000);
timespec tv; timespec tv;
clock_gettime(CLOCK_REALTIME, &tv); clock_gettime(CLOCK_REALTIME, &tv);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment