Commit 2a941eeb authored by Benoît Lizé's avatar Benoît Lizé Committed by Commit Bot

base/allocator: Add multi-threaded PartitionAlloc perftests.

This adds very simple multi-threaded PartitionAlloc tests, by having a separate
thread repeatedly allocate and free from the same allocator as the main test
one.

This is not intended to be a rigorous and extensive performance test, but to
validate and evaluate options to make multi-threaded scenarios better in
PartitionAlloc. In particular, the output is very noisy, as it heavily depends
on scheduling decisions (as the spinlocks we use are not even remotely
fair/priority aware).

Example output on Linux, Xeon "Haswell" 2.6GHz:

[ RUN      ] MemoryAllocationPerfTest.SingleBucket
*RESULT MemoryAllocationPerfTest single bucket allocation (40 bytes): = 28790830 runs/s
*RESULT MemoryAllocationPerfTest single bucket allocation (40 bytes): = 34 ns/run
[       OK ] MemoryAllocationPerfTest.SingleBucket (2883 ms)
[ RUN      ] MemoryAllocationPerfTest.SingleBucketWithCompetingThread
*RESULT MemoryAllocationPerfTest single bucket allocation (40 bytes): = 97388 runs/s
*RESULT MemoryAllocationPerfTest single bucket allocation (40 bytes): = 10268 ns/run
[       OK ] MemoryAllocationPerfTest.SingleBucketWithCompetingThread (2239 ms)
[ RUN      ] MemoryAllocationPerfTest.SingleBucketWithFree
*RESULT MemoryAllocationPerfTest single bucket allocation + free (40 bytes): = 42599320 runs/s
*RESULT MemoryAllocationPerfTest single bucket allocation + free (40 bytes): = 23 ns/run
[       OK ] MemoryAllocationPerfTest.SingleBucketWithFree (2000 ms)
[ RUN      ] MemoryAllocationPerfTest.SingleBucketWithFreeWithCompetingThread
*RESULT MemoryAllocationPerfTest single bucket allocation + free (40 bytes): = 593330 runs/s
*RESULT MemoryAllocationPerfTest single bucket allocation + free (40 bytes): = 1685 ns/run
[       OK ] MemoryAllocationPerfTest.SingleBucketWithFreeWithCompetingThread (2529 ms)
[ RUN      ] MemoryAllocationPerfTest.MultiBucket
*RESULT MemoryAllocationPerfTest multi-bucket allocation: = 9683520 runs/s
*RESULT MemoryAllocationPerfTest multi-bucket allocation: = 103 ns/run
[       OK ] MemoryAllocationPerfTest.MultiBucket (4143 ms)
[ RUN      ] MemoryAllocationPerfTest.MultiBucketWithCompetingThread
*RESULT MemoryAllocationPerfTest multi-bucket allocation: = 250140 runs/s
*RESULT MemoryAllocationPerfTest multi-bucket allocation: = 3997 ns/run
[       OK ] MemoryAllocationPerfTest.MultiBucketWithCompetingThread (23626 ms)
[ RUN      ] MemoryAllocationPerfTest.MultiBucketWithFree
*RESULT MemoryAllocationPerfTest multi-bucket allocation + free: = 51287040 runs/s
*RESULT MemoryAllocationPerfTest multi-bucket allocation + free: = 19 ns/run
[       OK ] MemoryAllocationPerfTest.MultiBucketWithFree (2016 ms)
[ RUN      ] MemoryAllocationPerfTest.MultiBucketWithFreeWithCompetingThread
*RESULT MemoryAllocationPerfTest multi-bucket allocation + free: = 690414 runs/s
*RESULT MemoryAllocationPerfTest multi-bucket allocation + free: = 1448 ns/run
[       OK ] MemoryAllocationPerfTest.MultiBucketWithFreeWithCompetingThread (6374 ms)

The results with a competing thread are very unstable from run to run, but
always at least an order of magnitude worse than without a competing thread.

Bug: 998048
Change-Id: I086d5559e6fd6b10db61cf20d0f5df0e855020c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1859989Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Commit-Queue: Benoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706357}
parent 18064026
......@@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atomic>
#include <vector>
#include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/lap_timer.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"
......@@ -28,19 +29,47 @@ constexpr int kMultiBucketIncrement = 13;
// Final size is 24 + (13 * 22) = 310 bytes.
constexpr int kMultiBucketRounds = 22;
class MemoryAllocationPerfTest : public testing::Test {
class AllocatingThread : public PlatformThread::Delegate {
public:
MemoryAllocationPerfTest()
: timer_(kWarmupRuns, kTimeLimit, kTimeCheckInterval) {}
void SetUp() override { alloc_.init(); }
void TearDown() override {
alloc_.root()->PurgeMemory(PartitionPurgeDecommitEmptyPages |
PartitionPurgeDiscardUnusedSystemPages);
explicit AllocatingThread(PartitionAllocatorGeneric* allocator)
: allocator_(allocator), should_stop_(false) {
PlatformThread::Create(0, this, &thread_handle_);
}
LapTimer timer_;
PartitionAllocatorGeneric alloc_;
~AllocatingThread() override {
should_stop_ = true;
PlatformThread::Join(thread_handle_);
}
// 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++;
}
}
PartitionAllocatorGeneric* allocator_;
std::atomic<bool> should_stop_;
PlatformThreadHandle thread_handle_;
};
void DisplayResults(const std::string& measurement,
const std::string& modifier,
size_t iterations_per_second) {
perf_test::PrintResult(measurement, modifier, "", iterations_per_second,
"runs/s", true);
perf_test::PrintResult(measurement, modifier, "",
static_cast<size_t>(1e9 / iterations_per_second),
"ns/run", true);
}
class MemoryAllocationPerfNode {
public:
MemoryAllocationPerfNode* GetNext() const { return next_; }
......@@ -59,10 +88,23 @@ class MemoryAllocationPerfNode {
MemoryAllocationPerfNode* next_ = nullptr;
};
TEST_F(MemoryAllocationPerfTest, SingleBucket) {
timer_.Reset();
MemoryAllocationPerfNode* first = reinterpret_cast<MemoryAllocationPerfNode*>(
class MemoryAllocationPerfTest : public testing::Test {
public:
MemoryAllocationPerfTest()
: timer_(kWarmupRuns, kTimeLimit, kTimeCheckInterval) {}
void SetUp() override { alloc_.init(); }
void TearDown() override {
alloc_.root()->PurgeMemory(PartitionPurgeDecommitEmptyPages |
PartitionPurgeDiscardUnusedSystemPages);
}
protected:
void TestSingleBucket() {
MemoryAllocationPerfNode* first =
reinterpret_cast<MemoryAllocationPerfNode*>(
alloc_.root()->Alloc(40, "<testing>"));
timer_.Reset();
MemoryAllocationPerfNode* cur = first;
do {
MemoryAllocationPerfNode* next =
......@@ -80,15 +122,16 @@ TEST_F(MemoryAllocationPerfTest, SingleBucket) {
MemoryAllocationPerfNode::FreeAll(first, alloc_);
perf_test::PrintResult("MemoryAllocationPerfTest",
" single bucket allocation (40 bytes)", "",
timer_.LapsPerSecond(), "runs/s", true);
}
DisplayResults("MemoryAllocationPerfTest",
" single bucket allocation (40 bytes)",
timer_.LapsPerSecond());
}
TEST_F(MemoryAllocationPerfTest, SingleBucketWithFree) {
timer_.Reset();
void TestSingleBucketWithFree() {
// Allocate an initial element to make sure the bucket stays set up.
void* elem = alloc_.root()->Alloc(40, "<testing>");
timer_.Reset();
do {
void* cur = alloc_.root()->Alloc(40, "<testing>");
CHECK_NE(cur, nullptr);
......@@ -97,22 +140,18 @@ TEST_F(MemoryAllocationPerfTest, SingleBucketWithFree) {
} while (!timer_.HasTimeLimitExpired());
alloc_.root()->Free(elem);
perf_test::PrintResult("MemoryAllocationPerfTest",
" single bucket allocation + free (40 bytes)", "",
timer_.LapsPerSecond(), "runs/s", true);
}
DisplayResults("MemoryAllocationPerfTest",
" single bucket allocation + free (40 bytes)",
timer_.LapsPerSecond());
}
// Failing on Nexus5x: crbug.com/949838
#if defined(OS_ANDROID)
#define MAYBE_MultiBucket DISABLED_MultiBucket
#else
#define MAYBE_MultiBucket MultiBucket
#endif
TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucket) {
timer_.Reset();
MemoryAllocationPerfNode* first = reinterpret_cast<MemoryAllocationPerfNode*>(
void TestMultiBucket() {
MemoryAllocationPerfNode* first =
reinterpret_cast<MemoryAllocationPerfNode*>(
alloc_.root()->Alloc(40, "<testing>"));
MemoryAllocationPerfNode* cur = first;
timer_.Reset();
do {
for (int i = 0; i < kMultiBucketRounds; i++) {
MemoryAllocationPerfNode* next =
......@@ -129,16 +168,15 @@ TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucket) {
MemoryAllocationPerfNode::FreeAll(first, alloc_);
perf_test::PrintResult("MemoryAllocationPerfTest", " multi-bucket allocation",
"", timer_.LapsPerSecond() * kMultiBucketRounds,
"runs/s", true);
}
DisplayResults("MemoryAllocationPerfTest", " multi-bucket allocation",
timer_.LapsPerSecond() * kMultiBucketRounds);
}
TEST_F(MemoryAllocationPerfTest, MultiBucketWithFree) {
timer_.Reset();
void TestMultiBucketWithFree() {
std::vector<void*> elems;
// Do an initial round of allocation to make sure that the buckets stay in use
// (and aren't accidentally released back to the OS).
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++) {
void* cur = alloc_.root()->Alloc(
kMultiBucketMinimumSize + (i * kMultiBucketIncrement), "<testing>");
......@@ -146,6 +184,7 @@ TEST_F(MemoryAllocationPerfTest, MultiBucketWithFree) {
elems.push_back(cur);
}
timer_.Reset();
do {
for (int i = 0; i < kMultiBucketRounds; i++) {
void* cur = alloc_.root()->Alloc(
......@@ -160,9 +199,58 @@ TEST_F(MemoryAllocationPerfTest, MultiBucketWithFree) {
alloc_.root()->Free(ptr);
}
perf_test::PrintResult(
"MemoryAllocationPerfTest", " multi-bucket allocation + free", "",
timer_.LapsPerSecond() * kMultiBucketRounds, "runs/s", true);
DisplayResults("MemoryAllocationPerfTest",
" multi-bucket allocation + free",
timer_.LapsPerSecond() * kMultiBucketRounds);
}
LapTimer timer_;
PartitionAllocatorGeneric alloc_;
};
TEST_F(MemoryAllocationPerfTest, SingleBucket) {
TestSingleBucket();
}
TEST_F(MemoryAllocationPerfTest, SingleBucketWithCompetingThread) {
AllocatingThread t(&alloc_);
TestSingleBucket();
}
TEST_F(MemoryAllocationPerfTest, SingleBucketWithFree) {
TestSingleBucketWithFree();
}
TEST_F(MemoryAllocationPerfTest, SingleBucketWithFreeWithCompetingThread) {
AllocatingThread t(&alloc_);
TestSingleBucketWithFree();
}
// 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
TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucket) {
TestMultiBucket();
}
TEST_F(MemoryAllocationPerfTest, MAYBE_MultiBucketWithCompetingThread) {
AllocatingThread t(&alloc_);
TestMultiBucket();
}
TEST_F(MemoryAllocationPerfTest, MultiBucketWithFree) {
TestMultiBucketWithFree();
}
TEST_F(MemoryAllocationPerfTest, MultiBucketWithFreeWithCompetingThread) {
AllocatingThread t(&alloc_);
TestMultiBucketWithFree();
}
} // anonymous namespace
......
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