1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <atomic>
6 #include <vector>
7
8 #include "base/allocator/partition_allocator/partition_alloc.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/threading/platform_thread.h"
11 #include "base/time/time.h"
12 #include "base/timer/lap_timer.h"
13 #include "build/build_config.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "testing/perf/perf_result_reporter.h"
16
17 namespace base {
18 namespace {
19
20 // Change kTimeLimit to something higher if you need more time to capture a
21 // trace.
22 constexpr base::TimeDelta kTimeLimit = base::TimeDelta::FromSeconds(2);
23 constexpr int kWarmupRuns = 5;
24 constexpr int kTimeCheckInterval = 100000;
25
26 // Size constants are mostly arbitrary, but try to simulate something like CSS
27 // parsing which consists of lots of relatively small objects.
28 constexpr int kMultiBucketMinimumSize = 24;
29 constexpr int kMultiBucketIncrement = 13;
30 // Final size is 24 + (13 * 22) = 310 bytes.
31 constexpr int kMultiBucketRounds = 22;
32
33 constexpr char kMetricPrefixMemoryAllocation[] = "MemoryAllocation";
34 constexpr char kMetricThroughput[] = "throughput";
35 constexpr char kMetricTimePerAllocation[] = "time_per_allocation";
36
SetUpReporter(const std::string & story_name)37 perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
38 perf_test::PerfResultReporter reporter(kMetricPrefixMemoryAllocation,
39 story_name);
40 reporter.RegisterImportantMetric(kMetricThroughput, "runs/s");
41 reporter.RegisterImportantMetric(kMetricTimePerAllocation, "ns");
42 return reporter;
43 }
44
45 enum class AllocatorType { kSystem, kPartitionAlloc };
46
47 class Allocator {
48 public:
49 Allocator() = default;
50 virtual ~Allocator() = default;
Init()51 virtual void Init() {}
52 virtual void* Alloc(size_t size) = 0;
53 virtual void Free(void* data) = 0;
54 };
55
56 class SystemAllocator : public Allocator {
57 public:
58 SystemAllocator() = default;
59 ~SystemAllocator() override = default;
Alloc(size_t size)60 void* Alloc(size_t size) override { return malloc(size); }
Free(void * data)61 void Free(void* data) override { free(data); }
62 };
63
64 class PartitionAllocator : public Allocator {
65 public:
PartitionAllocator()66 PartitionAllocator()
67 : alloc_(std::make_unique<PartitionAllocatorGeneric>()) {}
68 ~PartitionAllocator() override = default;
69
Init()70 void Init() override { alloc_->init(); }
Alloc(size_t size)71 void* Alloc(size_t size) override { return alloc_->root()->Alloc(size, ""); }
Free(void * data)72 void Free(void* data) override { return alloc_->root()->Free(data); }
73
74 private:
75 std::unique_ptr<PartitionAllocatorGeneric> alloc_;
76 };
77
78 class TestLoopThread : public PlatformThread::Delegate {
79 public:
TestLoopThread(OnceCallback<float ()> test_fn)80 explicit TestLoopThread(OnceCallback<float()> test_fn)
81 : test_fn_(std::move(test_fn)) {
82 CHECK(PlatformThread::Create(0, this, &thread_handle_));
83 }
84
Run()85 float Run() {
86 PlatformThread::Join(thread_handle_);
87 return laps_per_second_;
88 }
89
ThreadMain()90 void ThreadMain() override { laps_per_second_ = std::move(test_fn_).Run(); }
91
92 OnceCallback<float()> test_fn_;
93 PlatformThreadHandle thread_handle_;
94 std::atomic<float> laps_per_second_;
95 };
96
DisplayResults(const std::string & story_name,float iterations_per_second)97 void DisplayResults(const std::string& story_name,
98 float iterations_per_second) {
99 auto reporter = SetUpReporter(story_name);
100 reporter.AddResult(kMetricThroughput, iterations_per_second);
101 reporter.AddResult(kMetricTimePerAllocation,
102 static_cast<size_t>(1e9 / iterations_per_second));
103 }
104
105 class MemoryAllocationPerfNode {
106 public:
GetNext() const107 MemoryAllocationPerfNode* GetNext() const { return next_; }
SetNext(MemoryAllocationPerfNode * p)108 void SetNext(MemoryAllocationPerfNode* p) { next_ = p; }
FreeAll(MemoryAllocationPerfNode * first,Allocator * alloc)109 static void FreeAll(MemoryAllocationPerfNode* first, Allocator* alloc) {
110 MemoryAllocationPerfNode* cur = first;
111 while (cur != nullptr) {
112 MemoryAllocationPerfNode* next = cur->GetNext();
113 alloc->Free(cur);
114 cur = next;
115 }
116 }
117
118 private:
119 MemoryAllocationPerfNode* next_ = nullptr;
120 };
121
122 #if !defined(OS_ANDROID)
SingleBucket(Allocator * allocator)123 float SingleBucket(Allocator* allocator) {
124 auto* first =
125 reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40));
126
127 LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
128 MemoryAllocationPerfNode* cur = first;
129 do {
130 auto* next =
131 reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40));
132 CHECK_NE(next, nullptr);
133 cur->SetNext(next);
134 cur = next;
135 timer.NextLap();
136 } while (!timer.HasTimeLimitExpired());
137 // next_ = nullptr only works if the class constructor is called (it's not
138 // called in this case because then we can allocate arbitrary-length
139 // payloads.)
140 cur->SetNext(nullptr);
141
142 MemoryAllocationPerfNode::FreeAll(first, allocator);
143 return timer.LapsPerSecond();
144 }
145 #endif // defined(OS_ANDROID)
146
SingleBucketWithFree(Allocator * allocator)147 float SingleBucketWithFree(Allocator* allocator) {
148 // Allocate an initial element to make sure the bucket stays set up.
149 void* elem = allocator->Alloc(40);
150
151 LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
152 do {
153 void* cur = allocator->Alloc(40);
154 CHECK_NE(cur, nullptr);
155 allocator->Free(cur);
156 timer.NextLap();
157 } while (!timer.HasTimeLimitExpired());
158
159 allocator->Free(elem);
160 return timer.LapsPerSecond();
161 }
162
163 #if !defined(OS_ANDROID)
MultiBucket(Allocator * allocator)164 float MultiBucket(Allocator* allocator) {
165 auto* first =
166 reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(40));
167 MemoryAllocationPerfNode* cur = first;
168
169 LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
170 do {
171 for (int i = 0; i < kMultiBucketRounds; i++) {
172 auto* next = reinterpret_cast<MemoryAllocationPerfNode*>(allocator->Alloc(
173 kMultiBucketMinimumSize + (i * kMultiBucketIncrement)));
174 CHECK_NE(next, nullptr);
175 cur->SetNext(next);
176 cur = next;
177 }
178 timer.NextLap();
179 } while (!timer.HasTimeLimitExpired());
180 cur->SetNext(nullptr);
181
182 MemoryAllocationPerfNode::FreeAll(first, allocator);
183
184 return timer.LapsPerSecond() * kMultiBucketRounds;
185 }
186 #endif // defined(OS_ANDROID)
187
MultiBucketWithFree(Allocator * allocator)188 float MultiBucketWithFree(Allocator* allocator) {
189 std::vector<void*> elems;
190 elems.reserve(kMultiBucketRounds);
191 // Do an initial round of allocation to make sure that the buckets stay in
192 // use (and aren't accidentally released back to the OS).
193 for (int i = 0; i < kMultiBucketRounds; i++) {
194 void* cur =
195 allocator->Alloc(kMultiBucketMinimumSize + (i * kMultiBucketIncrement));
196 CHECK_NE(cur, nullptr);
197 elems.push_back(cur);
198 }
199
200 LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval);
201 do {
202 for (int i = 0; i < kMultiBucketRounds; i++) {
203 void* cur = allocator->Alloc(kMultiBucketMinimumSize +
204 (i * kMultiBucketIncrement));
205 CHECK_NE(cur, nullptr);
206 allocator->Free(cur);
207 }
208 timer.NextLap();
209 } while (!timer.HasTimeLimitExpired());
210
211 for (void* ptr : elems) {
212 allocator->Free(ptr);
213 }
214
215 return timer.LapsPerSecond() * kMultiBucketRounds;
216 }
217
CreateAllocator(AllocatorType type)218 std::unique_ptr<Allocator> CreateAllocator(AllocatorType type) {
219 if (type == AllocatorType::kSystem)
220 return std::make_unique<SystemAllocator>();
221 return std::make_unique<PartitionAllocator>();
222 }
223
RunTest(int thread_count,AllocatorType alloc_type,float (* test_fn)(Allocator *),const char * story_base_name)224 void RunTest(int thread_count,
225 AllocatorType alloc_type,
226 float (*test_fn)(Allocator*),
227 const char* story_base_name) {
228 auto alloc = CreateAllocator(alloc_type);
229 alloc->Init();
230
231 std::vector<std::unique_ptr<TestLoopThread>> threads;
232 for (int i = 0; i < thread_count; ++i) {
233 threads.push_back(std::make_unique<TestLoopThread>(
234 BindOnce(test_fn, Unretained(alloc.get()))));
235 }
236
237 uint64_t total_laps_per_second = 0;
238 uint64_t min_laps_per_second = std::numeric_limits<uint64_t>::max();
239 for (int i = 0; i < thread_count; ++i) {
240 uint64_t laps_per_second = threads[i]->Run();
241 min_laps_per_second = std::min(laps_per_second, min_laps_per_second);
242 total_laps_per_second += laps_per_second;
243 }
244
245 std::string name = base::StringPrintf(
246 "%s.%s_%s_%d", kMetricPrefixMemoryAllocation, story_base_name,
247 alloc_type == AllocatorType::kSystem ? "System" : "PartitionAlloc",
248 thread_count);
249
250 DisplayResults(name + "_total", total_laps_per_second);
251 DisplayResults(name + "_worst", min_laps_per_second);
252 }
253
254 class MemoryAllocationPerfTest
255 : public testing::TestWithParam<std::tuple<int, AllocatorType>> {};
256
257 INSTANTIATE_TEST_SUITE_P(
258 ,
259 MemoryAllocationPerfTest,
260 ::testing::Combine(::testing::Values(1, 2, 3, 4),
261 ::testing::Values(AllocatorType::kSystem,
262 AllocatorType::kPartitionAlloc)));
263
264 // This test (and the other one below) allocates a large amount of memory, which
265 // can cause issues on Android.
266 #if !defined(OS_ANDROID)
TEST_P(MemoryAllocationPerfTest,SingleBucket)267 TEST_P(MemoryAllocationPerfTest, SingleBucket) {
268 auto params = GetParam();
269 RunTest(std::get<0>(params), std::get<1>(params), SingleBucket,
270 "SingleBucket");
271 }
272 #endif
273
TEST_P(MemoryAllocationPerfTest,SingleBucketWithFree)274 TEST_P(MemoryAllocationPerfTest, SingleBucketWithFree) {
275 auto params = GetParam();
276 RunTest(std::get<0>(params), std::get<1>(params), SingleBucketWithFree,
277 "SingleBucketWithFree");
278 }
279
280 #if !defined(OS_ANDROID)
TEST_P(MemoryAllocationPerfTest,MultiBucket)281 TEST_P(MemoryAllocationPerfTest, MultiBucket) {
282 auto params = GetParam();
283 RunTest(std::get<0>(params), std::get<1>(params), MultiBucket, "MultiBucket");
284 }
285 #endif
286
TEST_P(MemoryAllocationPerfTest,MultiBucketWithFree)287 TEST_P(MemoryAllocationPerfTest, MultiBucketWithFree) {
288 auto params = GetParam();
289 RunTest(std::get<0>(params), std::get<1>(params), MultiBucketWithFree,
290 "MultiBucketWithFree");
291 }
292
293 } // namespace
294
295 } // namespace base
296