181 lines
5.2 KiB
C++
181 lines
5.2 KiB
C++
// Copyright 2021 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <atomic>
|
|
#include <string>
|
|
|
|
#include "base/barrier_closure.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/synchronization/lock.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "base/threading/simple_thread.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "testing/perf/perf_result_reporter.h"
|
|
|
|
// This file contains tests to measure the cost of incrementing:
|
|
// - A non-atomic variable, no lock.
|
|
// - A non-atomic variable, with lock.
|
|
// - An atomic variable, no memory barriers.
|
|
// - An atomic variable, acquire-release barriers.
|
|
// The goal is to provide data to guide counter implementation choices.
|
|
|
|
namespace base {
|
|
|
|
namespace {
|
|
|
|
constexpr char kMetricPrefixCounter[] = "Counter.";
|
|
constexpr char kMetricOperationThroughput[] = "operation_throughput";
|
|
constexpr uint64_t kNumIterations = 100000000;
|
|
|
|
perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
|
|
perf_test::PerfResultReporter reporter(kMetricPrefixCounter, story_name);
|
|
reporter.RegisterImportantMetric(kMetricOperationThroughput, "operations/ms");
|
|
return reporter;
|
|
}
|
|
|
|
class Uint64_NoLock {
|
|
public:
|
|
Uint64_NoLock() = default;
|
|
void Increment() { counter_ = counter_ + 1; }
|
|
uint64_t value() const { return counter_; }
|
|
|
|
private:
|
|
// Volatile to prevent the compiler from over-optimizing the increment.
|
|
volatile uint64_t counter_ = 0;
|
|
};
|
|
|
|
class Uint64_Lock {
|
|
public:
|
|
Uint64_Lock() = default;
|
|
void Increment() {
|
|
AutoLock auto_lock(lock_);
|
|
++counter_;
|
|
}
|
|
uint64_t value() const {
|
|
AutoLock auto_lock(lock_);
|
|
return counter_;
|
|
}
|
|
|
|
private:
|
|
mutable Lock lock_;
|
|
uint64_t counter_ GUARDED_BY(lock_) = 0;
|
|
};
|
|
|
|
class AtomicUint64_NoBarrier {
|
|
public:
|
|
AtomicUint64_NoBarrier() = default;
|
|
void Increment() { counter_.fetch_add(1, std::memory_order_relaxed); }
|
|
uint64_t value() const { return counter_; }
|
|
|
|
private:
|
|
std::atomic<uint64_t> counter_{0};
|
|
};
|
|
|
|
class AtomicUint64_Barrier {
|
|
public:
|
|
AtomicUint64_Barrier() = default;
|
|
void Increment() { counter_.fetch_add(1, std::memory_order_acq_rel); }
|
|
uint64_t value() const { return counter_; }
|
|
|
|
private:
|
|
std::atomic<uint64_t> counter_{0};
|
|
};
|
|
|
|
template <typename CounterType>
|
|
class IncrementThread : public SimpleThread {
|
|
public:
|
|
// Upon entering its main function, the thread waits for |start_event| to be
|
|
// signaled. Then, it increments |counter| |kNumIterations| times.
|
|
// Finally, it invokes |done_closure|.
|
|
explicit IncrementThread(WaitableEvent* start_event,
|
|
CounterType* counter,
|
|
OnceClosure done_closure)
|
|
: SimpleThread("IncrementThread"),
|
|
start_event_(start_event),
|
|
counter_(counter),
|
|
done_closure_(std::move(done_closure)) {}
|
|
|
|
// SimpleThread:
|
|
void Run() override {
|
|
start_event_->Wait();
|
|
for (uint64_t i = 0; i < kNumIterations; ++i)
|
|
counter_->Increment();
|
|
std::move(done_closure_).Run();
|
|
}
|
|
|
|
private:
|
|
const raw_ptr<WaitableEvent> start_event_;
|
|
const raw_ptr<CounterType> counter_;
|
|
OnceClosure done_closure_;
|
|
};
|
|
|
|
template <typename CounterType>
|
|
void RunIncrementPerfTest(const std::string& story_name, int num_threads) {
|
|
WaitableEvent start_event;
|
|
WaitableEvent end_event;
|
|
CounterType counter;
|
|
RepeatingClosure done_closure = BarrierClosure(
|
|
num_threads, BindOnce(&WaitableEvent::Signal, Unretained(&end_event)));
|
|
|
|
std::vector<std::unique_ptr<IncrementThread<CounterType>>> threads;
|
|
for (int i = 0; i < num_threads; ++i) {
|
|
threads.push_back(std::make_unique<IncrementThread<CounterType>>(
|
|
&start_event, &counter, done_closure));
|
|
threads.back()->Start();
|
|
}
|
|
|
|
TimeTicks start_time = TimeTicks::Now();
|
|
start_event.Signal();
|
|
end_event.Wait();
|
|
TimeTicks end_time = TimeTicks::Now();
|
|
|
|
EXPECT_EQ(num_threads * kNumIterations, counter.value());
|
|
|
|
auto reporter = SetUpReporter(story_name);
|
|
reporter.AddResult(
|
|
kMetricOperationThroughput,
|
|
kNumIterations / (end_time - start_time).InMillisecondsF());
|
|
|
|
for (auto& thread : threads)
|
|
thread->Join();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(CounterPerfTest, Uint64_NoLock_1Thread) {
|
|
RunIncrementPerfTest<Uint64_NoLock>("Uint64_NoLock_1Thread", 1);
|
|
}
|
|
|
|
// No Uint64_NoLock_4Threads test because it would cause data races.
|
|
|
|
TEST(CounterPerfTest, Uint64_Lock_1Thread) {
|
|
RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_1Thread", 1);
|
|
}
|
|
|
|
TEST(CounterPerfTest, Uint64_Lock_4Threads) {
|
|
RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_4Threads", 4);
|
|
}
|
|
|
|
TEST(CounterPerfTest, AtomicUint64_NoBarrier_1Thread) {
|
|
RunIncrementPerfTest<AtomicUint64_NoBarrier>("AtomicUint64_NoBarrier_1Thread",
|
|
1);
|
|
}
|
|
|
|
TEST(CounterPerfTest, AtomicUint64_NoBarrier_4Threads) {
|
|
RunIncrementPerfTest<AtomicUint64_NoBarrier>(
|
|
"AtomicUint64_NoBarrier_4Threads", 4);
|
|
}
|
|
|
|
TEST(CounterPerfTest, AtomicUint64_Barrier_1Thread) {
|
|
RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_1Thread", 1);
|
|
}
|
|
|
|
TEST(CounterPerfTest, AtomicUint64_Barrier_4Threads) {
|
|
RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_4Threads",
|
|
4);
|
|
}
|
|
|
|
} // namespace base
|