blob: 978ddb0e6e67e29d72699e689b3748c7d2b67eda [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/observer_list.h"
#include <memory>
#include "base/check_op.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_result_reporter.h"
// Ask the compiler not to use a register for this counter, in case it decides
// to do magic optimizations like |counter += kLaps|.
volatile int g_observer_list_perf_test_counter;
namespace base {
constexpr char kMetricPrefixObserverList[] = "ObserverList.";
constexpr char kMetricNotifyTimePerObserver[] = "notify_time_per_observer";
namespace {
perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
perf_test::PerfResultReporter reporter(kMetricPrefixObserverList, story_name);
reporter.RegisterImportantMetric(kMetricNotifyTimePerObserver, "ns");
return reporter;
}
} // namespace
class ObserverInterface {
public:
ObserverInterface() = default;
ObserverInterface(const ObserverInterface&) = delete;
ObserverInterface& operator=(const ObserverInterface&) = delete;
virtual ~ObserverInterface() = default;
virtual void Observe() const {
g_observer_list_perf_test_counter = g_observer_list_perf_test_counter + 1;
}
};
class UnsafeObserver : public ObserverInterface {};
class TestCheckedObserver : public CheckedObserver, public ObserverInterface {};
template <class ObserverType>
struct Pick {
// The ObserverList type to use. Checked observers need to be in a checked
// ObserverList.
using ObserverListType = ObserverList<ObserverType>;
static const char* GetName() { return "CheckedObserver"; }
};
template <>
struct Pick<UnsafeObserver> {
using ObserverListType = ObserverList<ObserverInterface>::Unchecked;
static const char* GetName() { return "UnsafeObserver"; }
};
template <class ObserverType>
class ObserverListPerfTest : public ::testing::Test {
public:
using ObserverListType = typename Pick<ObserverType>::ObserverListType;
ObserverListPerfTest() = default;
ObserverListPerfTest(const ObserverListPerfTest&) = delete;
ObserverListPerfTest& operator=(const ObserverListPerfTest&) = delete;
};
typedef ::testing::Types<UnsafeObserver, TestCheckedObserver> ObserverTypes;
TYPED_TEST_SUITE(ObserverListPerfTest, ObserverTypes);
// Performance test for base::ObserverList and Checked Observers.
TYPED_TEST(ObserverListPerfTest, NotifyPerformance) {
constexpr int kMaxObservers = 128;
#if DCHECK_IS_ON()
// The test takes about 100x longer in debug builds, mostly due to sequence
// checker overheads when WeakPtr gets involved.
constexpr int kLaps = 1000000;
#else
constexpr int kLaps = 100000000;
#endif
constexpr int kWarmupLaps = 100;
std::vector<std::unique_ptr<TypeParam>> observers;
for (int observer_count = 0; observer_count <= kMaxObservers;
observer_count = observer_count ? observer_count * 2 : 1) {
const int weighted_laps = kLaps / (observer_count + 1);
TimeDelta duration;
{
TimeTicks start;
typename TestFixture::ObserverListType list;
for (int i = 0; i < observer_count; ++i) {
observers.push_back(std::make_unique<TypeParam>());
}
for (auto& o : observers) {
list.AddObserver(o.get());
}
for (int i = 0; i < kWarmupLaps; ++i) {
for (auto& o : list) {
o.Observe();
}
}
g_observer_list_perf_test_counter = 0;
start = TimeTicks::Now();
for (int i = 0; i < weighted_laps; ++i) {
for (auto& o : list) {
o.Observe();
}
}
duration = TimeTicks::Now() - start;
}
// The observers are no longer needed in this iteration, so reset the list
// to get ready for the next iteration. Be careful. We cannot invoke
// `observers.clear()` before destructing the `list`. Otherwise, we will
// see crashes caused by dangling pointers.
observers.clear();
EXPECT_EQ(observer_count * weighted_laps,
g_observer_list_perf_test_counter);
std::string story_name =
base::StringPrintf("%s_%d", Pick<TypeParam>::GetName(), observer_count);
// A typical value is 3-20 nanoseconds per observe in Release, 1000-2000ns
// in an optimized build with DCHECKs and 3000-6000ns in debug builds.
auto reporter = SetUpReporter(story_name);
reporter.AddResult(
kMetricNotifyTimePerObserver,
duration.InNanoseconds() /
static_cast<double>(g_observer_list_perf_test_counter +
weighted_laps));
}
}
} // namespace base