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