src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 115
0.0%
Branches:
0 / 102
0.0%
Functions:
0 / 7
0.0%

memory/allocators/benchmark_malloc.cc
Line Branch Exec Source
1 #include <unistd.h>
2
3 #include <algorithm>
4 #include <atomic>
5 #include <chrono>
6 #include <cstdio>
7 #include <memory>
8 #include <string>
9 #include <thread>
10 #include <vector>
11
12 #include "base/base.h"
13 #include "base/bind_core.h"
14 #include "base/factory.h"
15 #include "base/init.h"
16 #include "memory/allocators/concurrent_slab_memory_pool.h"
17 #include "memory/allocators/persist_loop_slab_allocator.h"
18 #include "memory/allocators/r2_slab_allocator.h"
19 #include "memory/malloc.h"
20
21 DEFINE_string(
22 allocators,
23 "CONCURRENT_SLAB_MEMORY_POOL,R2_SLAB,PERSIST_LOOP_SLAB,PERSIST_MEMORY_POOL",
24 "Comma-separated MallocApi factory names to benchmark");
25 DEFINE_int32(thread_num, 16, "Number of concurrent worker threads");
26 DEFINE_int32(alloc_size,
27 128,
28 "Allocation size in bytes for each New/Free pair");
29 DEFINE_int32(warmup_seconds, 1, "Warmup duration in seconds");
30 DEFINE_int32(running_seconds, 5, "Measured benchmark duration in seconds");
31 DEFINE_int64(pool_bytes,
32 256LL * 1024 * 1024,
33 "Backing memory pool size in bytes");
34 DEFINE_string(medium, "DRAM", "Shm medium passed to MallocApi factory");
35
36 namespace {
37
38 using MallocFactory = base::
39 Factory<base::MallocApi, const std::string&, int64, const std::string&>;
40
41 struct BenchResult {
42 std::string allocator;
43 int threads = 0;
44 int alloc_size = 0;
45 double elapsed_sec = 0.0;
46 uint64_t total_ops = 0;
47 uint64_t failures = 0;
48 double ops_per_sec = 0.0;
49 double alloc_mb_per_sec = 0.0;
50 bool ok = false;
51 std::string error;
52 };
53
54 ✗ std::vector<std::string> SplitCommaList(const std::string& value) {
55 ✗ std::vector<std::string> items;
56 ✗ std::string current;
57
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (char c : value) {
58
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (c == ',') {
59
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!current.empty()) {
60
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ items.push_back(current);
61 ✗ current.clear();
62 }
63 ✗ continue;
64 }
65
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (c != ' ') {
66
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ current.push_back(c);
67 }
68 }
69
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!current.empty()) {
70
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ items.push_back(current);
71 }
72 ✗ return items;
73 ✗ }
74
75 ✗ std::string BenchPath(const std::string& allocator) {
76
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ return "/tmp/recstore_malloc_bench_" + allocator + "_" +
77
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ std::to_string(getpid());
78 }
79
80 ✗ void RunTimedWorkers(base::MallocApi* allocator,
81 int alloc_size,
82 int duration_seconds,
83 std::vector<uint64_t>* per_thread_ops,
84 std::vector<uint64_t>* per_thread_failures) {
85 ✗ std::atomic<bool> stop{false};
86 ✗ const int thread_num = static_cast<int>(per_thread_ops->size());
87
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ std::fill(per_thread_ops->begin(), per_thread_ops->end(), 0);
88
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ std::fill(per_thread_failures->begin(), per_thread_failures->end(), 0);
89
90 ✗ std::vector<std::thread> threads;
91
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ threads.reserve(static_cast<size_t>(thread_num));
92
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (int tid = 0; tid < thread_num; ++tid) {
93
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ threads.emplace_back(
94 ✗ [allocator,
95 alloc_size,
96 &stop,
97 tid,
98 per_thread_ops,
99 ✗ per_thread_failures]() {
100 ✗ base::auto_bind_core();
101 ✗ uint64_t ops = 0;
102 ✗ uint64_t failures = 0;
103
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ while (!stop.load(std::memory_order_acquire)) {
104 ✗ char* ptr = allocator->New(alloc_size);
105
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (ptr == nullptr) {
106 ✗ ++failures;
107 ✗ continue;
108 }
109
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!allocator->Free(ptr)) {
110 ✗ ++failures;
111 }
112 ✗ ++ops;
113 }
114 ✗ (*per_thread_ops)[static_cast<size_t>(tid)] = ops;
115 ✗ (*per_thread_failures)[static_cast<size_t>(tid)] = failures;
116 ✗ });
117 }
118
119
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ std::this_thread::sleep_for(std::chrono::seconds(duration_seconds));
120 ✗ stop.store(true, std::memory_order_release);
121
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (auto& thread : threads) {
122
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ thread.join();
123 }
124 ✗ }
125
126 ✗ BenchResult BenchmarkAllocator(const std::string& allocator_name) {
127 ✗ BenchResult result;
128
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ result.allocator = allocator_name;
129 ✗ result.threads = FLAGS_thread_num;
130 ✗ result.alloc_size = FLAGS_alloc_size;
131
132
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const std::string path = BenchPath(allocator_name);
133
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ base::file_util::Delete(path, false);
134
135 std::unique_ptr<base::MallocApi> allocator(MallocFactory::NewInstance(
136
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ allocator_name, path, FLAGS_pool_bytes, FLAGS_medium));
137
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!allocator) {
138
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ result.error = "factory returned nullptr";
139 ✗ return result;
140 }
141
142 std::vector<uint64_t> per_thread_ops(
143
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ static_cast<size_t>(FLAGS_thread_num), 0);
144 std::vector<uint64_t> per_thread_failures(
145
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ static_cast<size_t>(FLAGS_thread_num), 0);
146
147
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (FLAGS_warmup_seconds > 0) {
148
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ RunTimedWorkers(
149 allocator.get(),
150 FLAGS_alloc_size,
151 FLAGS_warmup_seconds,
152 &per_thread_ops,
153 &per_thread_failures);
154 }
155
156 ✗ const auto start = std::chrono::steady_clock::now();
157
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ RunTimedWorkers(
158 allocator.get(),
159 FLAGS_alloc_size,
160 FLAGS_running_seconds,
161 &per_thread_ops,
162 &per_thread_failures);
163 ✗ const auto end = std::chrono::steady_clock::now();
164
165
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ result.elapsed_sec = std::chrono::duration<double>(end - start).count();
166
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (size_t tid = 0; tid < per_thread_ops.size(); ++tid) {
167 ✗ result.total_ops += per_thread_ops[tid];
168 ✗ result.failures += per_thread_failures[tid];
169 }
170
171
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (result.elapsed_sec > 0.0) {
172 ✗ result.ops_per_sec =
173 ✗ static_cast<double>(result.total_ops) / result.elapsed_sec;
174 ✗ result.alloc_mb_per_sec =
175 ✗ (static_cast<double>(result.total_ops) * FLAGS_alloc_size) /
176 ✗ result.elapsed_sec / (1024.0 * 1024.0);
177 }
178
179 ✗ result.ok = true;
180 ✗ allocator.reset();
181
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ base::file_util::Delete(path, false);
182 ✗ return result;
183 ✗ }
184
185 ✗ void PrintSummaryTable(const std::vector<BenchResult>& results) {
186 ✗ printf("\nMallocApi concurrent New/Free benchmark summary\n");
187 ✗ printf("threads=%d alloc_size=%d warmup=%ds running=%ds pool_bytes=%ld\n\n",
188 FLAGS_thread_num,
189 FLAGS_alloc_size,
190 FLAGS_warmup_seconds,
191 FLAGS_running_seconds,
192 static_cast<long>(FLAGS_pool_bytes));
193
194 ✗ printf("%-28s %7s %7s %9s %12s %9s %14s %12s\n",
195 "Allocator",
196 "Threads",
197 "Size(B)",
198 "Seconds",
199 "TotalOps",
200 "Failures",
201 "Ops/s",
202 "AllocMB/s");
203 ✗ printf("%-28s %7s %7s %9s %12s %9s %14s %12s\n",
204 "----------------------------",
205 "-------",
206 "-------",
207 "---------",
208 "------------",
209 "---------",
210 "--------------",
211 "------------");
212
213
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (const BenchResult& result : results) {
214
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!result.ok) {
215
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ printf("%-28s %7d %7d %9s %12s %9s %14s %12s (%s)\n",
216 result.allocator.c_str(),
217 ✗ result.threads,
218 ✗ result.alloc_size,
219 "-",
220 "-",
221 "-",
222 "-",
223 "-",
224 result.error.c_str());
225 ✗ continue;
226 }
227
228
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ printf("%-28s %7d %7d %9.3f %12lu %9lu %14.0f %12.2f\n",
229 result.allocator.c_str(),
230 ✗ result.threads,
231 ✗ result.alloc_size,
232 ✗ result.elapsed_sec,
233 ✗ static_cast<unsigned long>(result.total_ops),
234 ✗ static_cast<unsigned long>(result.failures),
235 ✗ result.ops_per_sec,
236 ✗ result.alloc_mb_per_sec);
237 }
238 ✗ printf("\n");
239 ✗ }
240
241 } // namespace
242
243 ✗ int main(int argc, char** argv) {
244
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ base::Init(&argc, &argv);
245
246 const std::vector<std::string> allocator_names =
247
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ SplitCommaList(FLAGS_allocators);
248
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (allocator_names.empty()) {
249
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ fprintf(stderr, "No allocators specified via --allocators\n");
250 ✗ return 1;
251 }
252
253 ✗ std::vector<BenchResult> results;
254
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ results.reserve(allocator_names.size());
255
0/2
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ for (const std::string& allocator_name : allocator_names) {
256
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ results.push_back(BenchmarkAllocator(allocator_name));
257 }
258
259
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ PrintSummaryTable(results);
260 ✗ return 0;
261 ✗ }
262