Commit 113ebf6d authored by Salvador Guerrero's avatar Salvador Guerrero Committed by Commit Bot

Revert "base/allocator: Improve PartitionAlloc perftests."

This reverts commit ea08b9b5.

Reason for revert: caused build failure in arm64-builder-rel
https://ci.chromium.org/p/chrome/builders/ci/arm64-builder-rel/21172

Original change's description:
> base/allocator: Improve PartitionAlloc perftests.
> 
> Add:
> - Comparison with the system allocator
> - Better concurrent workloads for competing threads (identical as the
>   test)
> - Reporting of the total allocation rate, and the worst thread
> - Varying number of competing threads
> 
> This is useful to evaluate the performance of PartitionAlloc with
> various changes, such as changing its locking to base::Lock, or
> implementing a thread cache.
> 
> Bug: 998048
> Change-Id: I4c5f837f9e1b5af19b2f80bd79f3d3181166d930
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2111160
> Reviewed-by: Kentaro Hara <haraken@chromium.org>
> Commit-Queue: Benoit L <lizeb@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#752077}

TBR=haraken@chromium.org,lizeb@chromium.org

Change-Id: I02421083b4765a68138544307ba238aa481b0a41
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 998048
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2112589Reviewed-by: default avatarSalvador Guerrero <salg@google.com>
Commit-Queue: Salvador Guerrero <salg@google.com>
Cr-Commit-Position: refs/heads/master@{#752090}
parent 429fef58
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include <vector> #include <vector>
#include "base/allocator/partition_allocator/partition_alloc.h" #include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/lap_timer.h" #include "base/timer/lap_timer.h"
...@@ -30,9 +29,14 @@ constexpr int kMultiBucketIncrement = 13; ...@@ -30,9 +29,14 @@ constexpr int kMultiBucketIncrement = 13;
// Final size is 24 + (13 * 22) = 310 bytes. // Final size is 24 + (13 * 22) = 310 bytes.
constexpr int kMultiBucketRounds = 22; constexpr int kMultiBucketRounds = 22;
constexpr char kMetricPrefixMemoryAllocation[] = "MemoryAllocation"; constexpr char kMetricPrefixMemoryAllocation[] = "MemoryAllocation.";
constexpr char kMetricThroughput[] = "throughput"; constexpr char kMetricThroughput[] = "throughput";
constexpr char kMetricTimePerAllocation[] = "time_per_allocation"; constexpr char kMetricTimePerAllocation[] = "time_per_allocation";
constexpr char kStoryBaseSingleBucket[] = "single_bucket";
constexpr char kStoryBaseSingleBucketWithFree[] = "single_bucket_with_free";
constexpr char kStoryBaseMultiBucket[] = "multi_bucket";
constexpr char kStoryBaseMultiBucketWithFree[] = "multi_bucket_with_free";
constexpr char kStorySuffixWithCompetingThread[] = "_with_competing_thread";
perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
perf_test::PerfResultReporter reporter(kMetricPrefixMemoryAllocation, perf_test::PerfResultReporter reporter(kMetricPrefixMemoryAllocation,
...@@ -42,60 +46,43 @@ perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { ...@@ -42,60 +46,43 @@ perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
return reporter; return reporter;
} }
enum class AllocatorType { kSystem, kPartitionAlloc }; std::string GetSuffix(bool competing_thread) {
return competing_thread ? kStorySuffixWithCompetingThread : "";
class Allocator { }
public:
Allocator() = default;
virtual ~Allocator() = default;
virtual void Init() {}
virtual void* Alloc(size_t size) = 0;
virtual void Free(void* data) = 0;
};
class SystemAllocator : public Allocator {
public:
SystemAllocator() = default;
~SystemAllocator() override = default;
void* Alloc(size_t size) override { return malloc(size); }
void Free(void* data) override { free(data); }
};
class PartitionAllocator : public Allocator {
public:
PartitionAllocator()
: alloc_(std::make_unique<PartitionAllocatorGeneric>()) {}
~PartitionAllocator() override = default;
void Init() override { alloc_->init(); }
void* Alloc(size_t size) override { return alloc_->root()->Alloc(size, ""); }
void Free(void* data) override { return alloc_->root()->Free(data); }
private:
std::unique_ptr<PartitionAllocatorGeneric> alloc_;
};
class TestLoopThread : public PlatformThread::Delegate { class AllocatingThread : public PlatformThread::Delegate {
public: public:
explicit TestLoopThread(OnceCallback<float()> test_fn) explicit AllocatingThread(PartitionAllocatorGeneric* allocator)
: test_fn_(std::move(test_fn)) { : allocator_(allocator), should_stop_(false) {
CHECK(PlatformThread::Create(0, this, &thread_handle_)); PlatformThread::Create(0, this, &thread_handle_);
} }
float Run() { ~AllocatingThread() override {
should_stop_ = true;
PlatformThread::Join(thread_handle_); PlatformThread::Join(thread_handle_);
return laps_per_second_;
} }
void ThreadMain() override { laps_per_second_ = std::move(test_fn_).Run(); } // Allocates and frees memory in a loop until |should_stop_| becomes true.
void ThreadMain() override {
uint64_t count = 0;
while (true) {
// Only check |should_stop_| every 2^15 iterations, as it is a
// sequentially consistent access, hence expensive.
if (count % (1 << 15) == 0 && should_stop_)
break;
void* data = allocator_->root()->Alloc(10, "");
allocator_->root()->Free(data);
count++;
}
}
OnceCallback<float()> test_fn_; PartitionAllocatorGeneric* allocator_;
std::atomic<bool> should_stop_;
PlatformThreadHandle thread_handle_; PlatformThreadHandle thread_handle_;
std::atomic<float> laps_per_second_;
}; };
void DisplayResults(const std::string& story_name, void DisplayResults(const std::string& story_name,
float iterations_per_second) { size_t iterations_per_second) {
auto reporter = SetUpReporter(story_name); auto reporter = SetUpReporter(story_name);
reporter.AddResult(kMetricThroughput, iterations_per_second); reporter.AddResult(kMetricThroughput, iterations_per_second);
reporter.AddResult(kMetricTimePerAllocation, reporter.AddResult(kMetricTimePerAllocation,
...@@ -106,11 +93,12 @@ class MemoryAllocationPerfNode { ...@@ -106,11 +93,12 @@ class MemoryAllocationPerfNode {
public: public:
MemoryAllocationPerfNode* GetNext() const { return next_; } MemoryAllocationPerfNode* GetNext() const { return next_; }
void SetNext(MemoryAllocationPerfNode* p) { next_ = p; } void SetNext(MemoryAllocationPerfNode* p) { next_ = p; }
static void FreeAll(MemoryAllocationPerfNode* first, Allocator* alloc) { static void FreeAll(MemoryAllocationPerfNode* first,
PartitionAllocatorGeneric& alloc) {
MemoryAllocationPerfNode* cur = first; MemoryAllocationPerfNode* cur = first;
while (cur != nullptr) { while (cur != nullptr) {
MemoryAllocationPerfNode* next = cur->GetNext(); MemoryAllocationPerfNode* next = cur->GetNext();
alloc->Free(cur); alloc.root()->Free(cur);
cur = next; cur = next;
} }
} }
...@@ -119,173 +107,172 @@ class MemoryAllocationPerfNode { ...@@ -119,173 +107,172 @@ class MemoryAllocationPerfNode {
MemoryAllocationPerfNode* next_ = nullptr; MemoryAllocationPerfNode* next_ = nullptr;
}; };
float SingleBucket(Allocator* allocator) { class MemoryAllocationPerfTest : public testing::Test {
auto* first = public:
reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40)); MemoryAllocationPerfTest()
: timer_(kWarmupRuns, kTimeLimit, kTimeCheckInterval) {}
LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval); void SetUp() override { alloc_.init(); }
MemoryAllocationPerfNode* cur = first; void TearDown() override {
do { alloc_.root()->PurgeMemory(PartitionPurgeDecommitEmptyPages |
auto* next = PartitionPurgeDiscardUnusedSystemPages);
reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40)); }
CHECK_NE(next, nullptr);
cur->SetNext(next);
cur = next;
timer.NextLap();
} while (!timer.HasTimeLimitExpired());
// next_ = nullptr only works if the class constructor is called (it's not
// called in this case because then we can allocate arbitrary-length
// payloads.)
cur->SetNext(nullptr);
MemoryAllocationPerfNode::FreeAll(first, allocator);
return timer.LapsPerSecond();
}
float SingleBucketWithFree(Allocator* allocator) {
// Allocate an initial element to make sure the bucket stays set up.
void* elem = allocator->Alloc(40);
LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
do {
void* cur = allocator->Alloc(40);
CHECK_NE(cur, nullptr);
allocator->Free(cur);
timer.NextLap();
} while (!timer.HasTimeLimitExpired());
allocator->Free(elem);
return timer.LapsPerSecond();
}
float MultiBucket(Allocator* allocator) { protected:
auto* first = void TestSingleBucket(bool competing_thread) {
reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40)); MemoryAllocationPerfNode* first =
MemoryAllocationPerfNode* cur = first; reinterpret_cast<MemoryAllocationPerfNode*>(
alloc_.root()->Alloc(40, "<testing>"));
LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval); timer_.Reset();
do { MemoryAllocationPerfNode* cur = first;
for (int i = 0; i < kMultiBucketRounds; i++) { do {
auto* next = reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc( MemoryAllocationPerfNode* next =
kMultiBucketMinimumSize + (i * kMultiBucketIncrement))); reinterpret_cast<MemoryAllocationPerfNode*>(
alloc_.root()->Alloc(40, "<testing>"));
CHECK_NE(next, nullptr); CHECK_NE(next, nullptr);
cur->SetNext(next); cur->SetNext(next);
cur = next; cur = next;
} timer_.NextLap();
timer.NextLap(); } while (!timer_.HasTimeLimitExpired());
} while (!timer.HasTimeLimitExpired()); // next_ = nullptr only works if the class constructor is called (it's not
cur->SetNext(nullptr); // called in this case because then we can allocate arbitrary-length
// payloads.)
cur->SetNext(nullptr);
MemoryAllocationPerfNode::FreeAll(first, alloc_);
DisplayResults(
std::string(kStoryBaseSingleBucket) + GetSuffix(competing_thread),
timer_.LapsPerSecond());
}
MemoryAllocationPerfNode::FreeAll(first, allocator); void TestSingleBucketWithFree(bool competing_thread) {
// Allocate an initial element to make sure the bucket stays set up.
void* elem = alloc_.root()->Alloc(40, "<testing>");
return timer.LapsPerSecond() * kMultiBucketRounds; timer_.Reset();
} do {
void* cur = alloc_.root()->Alloc(40, "<testing>");
CHECK_NE(cur, nullptr);
alloc_.root()->Free(cur);
timer_.NextLap();
} while (!timer_.HasTimeLimitExpired());
alloc_.root()->Free(elem);
DisplayResults(std::string(kStoryBaseSingleBucketWithFree) +
GetSuffix(competing_thread),
timer_.LapsPerSecond());
}
void TestMultiBucket(bool competing_thread) {
MemoryAllocationPerfNode* first =
reinterpret_cast<MemoryAllocationPerfNode*>(
alloc_.root()->Alloc(40, "<testing>"));
MemoryAllocationPerfNode* cur = first;
float MultiBucketWithFree(Allocator* allocator) { timer_.Reset();
std::vector<void*> elems; do {
elems.reserve(kMultiBucketRounds); for (int i = 0; i < kMultiBucketRounds; i++) {
// Do an initial round of allocation to make sure that the buckets stay in MemoryAllocationPerfNode* next =
// use (and aren't accidentally released back to the OS). reinterpret_cast<MemoryAllocationPerfNode*>(alloc_.root()->Alloc(
for (int i = 0; i < kMultiBucketRounds; i++) { kMultiBucketMinimumSize + (i * kMultiBucketIncrement),
void* cur = "<testing>"));
allocator->Alloc(kMultiBucketMinimumSize + (i * kMultiBucketIncrement)); CHECK_NE(next, nullptr);
CHECK_NE(cur, nullptr); cur->SetNext(next);
elems.push_back(cur); cur = next;
}
timer_.NextLap();
} while (!timer_.HasTimeLimitExpired());
cur->SetNext(nullptr);
MemoryAllocationPerfNode::FreeAll(first, alloc_);
DisplayResults(
std::string(kStoryBaseMultiBucket) + GetSuffix(competing_thread),
timer_.LapsPerSecond() * kMultiBucketRounds);
} }
LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval); void TestMultiBucketWithFree(bool competing_thread) {
do { std::vector<void*> elems;
elems.reserve(kMultiBucketRounds);
// Do an initial round of allocation to make sure that the buckets stay in
// use (and aren't accidentally released back to the OS).
for (int i = 0; i < kMultiBucketRounds; i++) { for (int i = 0; i < kMultiBucketRounds; i++) {
void* cur = allocator->Alloc(kMultiBucketMinimumSize + void* cur = alloc_.root()->Alloc(
(i * kMultiBucketIncrement)); kMultiBucketMinimumSize + (i * kMultiBucketIncrement), "<testing>");
CHECK_NE(cur, nullptr); CHECK_NE(cur, nullptr);
allocator->Free(cur); elems.push_back(cur);
} }
timer.NextLap();
} while (!timer.HasTimeLimitExpired()); timer_.Reset();
do {
for (int i = 0; i < kMultiBucketRounds; i++) {
void* cur = alloc_.root()->Alloc(
kMultiBucketMinimumSize + (i * kMultiBucketIncrement), "<testing>");
CHECK_NE(cur, nullptr);
alloc_.root()->Free(cur);
}
timer_.NextLap();
} while (!timer_.HasTimeLimitExpired());
for (void* ptr : elems) { for (void* ptr : elems) {
allocator->Free(ptr); alloc_.root()->Free(ptr);
} }
return timer.LapsPerSecond() * kMultiBucketRounds; DisplayResults(std::string(kStoryBaseMultiBucketWithFree) +
} GetSuffix(competing_thread),
timer_.LapsPerSecond() * kMultiBucketRounds);
std::unique_ptr<Allocator> CreateAllocator(AllocatorType type) {
if (type == AllocatorType::kSystem)
return std::make_unique<SystemAllocator>();
return std::make_unique<PartitionAllocator>();
}
void RunTest(int thread_count,
AllocatorType alloc_type,
float (*test_fn)(Allocator*),
const char* story_base_name) {
auto alloc = CreateAllocator(alloc_type);
alloc->Init();
std::vector<std::unique_ptr<TestLoopThread>> threads;
for (int i = 0; i < thread_count; ++i) {
threads.push_back(std::make_unique<TestLoopThread>(
BindOnce(test_fn, Unretained(alloc.get()))));
} }
uint64_t total_laps_per_second = 0; LapTimer timer_;
uint64_t min_laps_per_second = std::numeric_limits<uint64_t>::max(); PartitionAllocatorGeneric alloc_;
for (int i = 0; i < thread_count; ++i) { };
uint64_t laps_per_second = threads[i]->Run();
min_laps_per_second = std::min(laps_per_second, min_laps_per_second);
total_laps_per_second += laps_per_second;
}
std::string name = base::StringPrintf( TEST_F(MemoryAllocationPerfTest, SingleBucket) {
"%s.%s_%s_%d", kMetricPrefixMemoryAllocation, story_base_name, TestSingleBucket(false);
alloc_type == AllocatorType::kSystem ? "System" : "PartitionAlloc", }
thread_count);
DisplayResults(name + "_total", total_laps_per_second); TEST_F(MemoryAllocationPerfTest, SingleBucketWithCompetingThread) {
DisplayResults(name + "_worst", min_laps_per_second); AllocatingThread t(&alloc_);
TestSingleBucket(true);
} }
class MemoryAllocationPerfTest TEST_F(MemoryAllocationPerfTest, SingleBucketWithFree) {
: public testing::TestWithParam<std::tuple<int, AllocatorType>> {}; TestSingleBucketWithFree(false);
INSTANTIATE_TEST_SUITE_P(
,
MemoryAllocationPerfTest,
::testing::Combine(::testing::Values(1, 2, 3, 4),
::testing::Values(AllocatorType::kSystem,
AllocatorType::kPartitionAlloc)));
// This test (and the other one below) allocates a large amount of memory, which
// can cause issues on Android.
#if !defined(OS_ANDROID)
TEST_P(MemoryAllocationPerfTest, SingleBucket) {
auto params = GetParam();
RunTest(std::get<0>(params), std::get<1>(params), SingleBucket,
"SingleBucket");
} }
TEST_F(MemoryAllocationPerfTest, SingleBucketWithFreeWithCompetingThread) {
AllocatingThread t(&alloc_);
TestSingleBucketWithFree(true);
}
// Failing on Nexus5x: crbug.com/949838
#if defined(OS_ANDROID)
#define MAYBE_MultiBucket DISABLED_MultiBucket
#define MAYBE_MultiBucketWithCompetingThread \
DISABLED_MultiBucketWithCompetingThread
#else
#define MAYBE_MultiBucket MultiBucket
#define MAYBE_MultiBucketWithCompetingThread MultiBucketWithCompetingThread
#endif #endif
TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucket) {
TestMultiBucket(false);
}
TEST_P(MemoryAllocationPerfTest, SingleBucketWithFree) { TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucketWithCompetingThread) {
auto params = GetParam(); AllocatingThread t(&alloc_);
RunTest(std::get<0>(params), std::get<1>(params), SingleBucketWithFree, TestMultiBucket(true);
"SingleBucketWithFree");
} }
#if !defined(OS_ANDROID) TEST_F(MemoryAllocationPerfTest, MultiBucketWithFree) {
TEST_P(MemoryAllocationPerfTest, MultiBucket) { TestMultiBucketWithFree(false);
auto params = GetParam();
RunTest(std::get<0>(params), std::get<1>(params), MultiBucket, "MultiBucket");
} }
#endif
TEST_P(MemoryAllocationPerfTest, MultiBucketWithFree) { TEST_F(MemoryAllocationPerfTest, MultiBucketWithFreeWithCompetingThread) {
auto params = GetParam(); AllocatingThread t(&alloc_);
RunTest(std::get<0>(params), std::get<1>(params), MultiBucketWithFree, TestMultiBucketWithFree(true);
"MultiBucketWithFree");
} }
} // namespace } // anonymous namespace
} // namespace base } // namespace base
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