1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <string.h>
18 #include <sys/mman.h>
19 #include <sys/syscall.h>
20
21 #include <android-base/file.h>
22 #include <android-base/stringprintf.h>
23 #include <benchmark/benchmark.h>
24
25 #include "util.h"
26
27 enum BenchmarkType : uint8_t {
28 kBenchmarkMmapOnly,
29 kBenchmarkMunmapOnly,
30 kBenchmarkAll,
31 };
32
33 static size_t page_sz = getpagesize();
34
35 struct MmapParams {
36 int prot;
37 int flags;
38 int64_t size;
39 };
40
41 struct MprotectParams {
42 int initial_prot;
43 int mprotect_prot;
44 int64_t size;
45 };
46
47 template <BenchmarkType type>
MmapBenchmarkImpl(benchmark::State & state,const struct MmapParams & params,int fd,void * area=nullptr)48 void MmapBenchmarkImpl(benchmark::State& state, const struct MmapParams& params, int fd,
49 void* area = nullptr) {
50 for (auto _ : state) {
51 if (type == kBenchmarkMunmapOnly) state.PauseTiming();
52 void* addr = mmap(area, params.size, params.prot, params.flags, fd, 0);
53 if (addr == MAP_FAILED) {
54 state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
55 break;
56 }
57
58 if (type == kBenchmarkMmapOnly) state.PauseTiming();
59
60 if (params.prot & PROT_WRITE) {
61 MakeAllocationResident(addr, params.size, page_sz);
62 }
63
64 if (type == kBenchmarkMunmapOnly) state.ResumeTiming();
65
66 if (munmap(addr, params.size) != 0) {
67 state.SkipWithError(
68 android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
69 break;
70 }
71 if (type == kBenchmarkMmapOnly) state.ResumeTiming();
72 }
73 }
74
MmapBenchmark(benchmark::State & state,const struct MmapParams & params,int fd,void * area=nullptr)75 static void MmapBenchmark(benchmark::State& state, const struct MmapParams& params, int fd,
76 void* area = nullptr) {
77 MmapBenchmarkImpl<kBenchmarkAll>(state, params, fd, area);
78 }
79
MmapFixedBenchmark(benchmark::State & state,const struct MmapParams & params,int fd,size_t area_size,size_t offs)80 static void MmapFixedBenchmark(benchmark::State& state, const struct MmapParams& params, int fd,
81 size_t area_size, size_t offs) {
82 if ((params.flags & MAP_FIXED) == 0) {
83 state.SkipWithError("MmapFixedBenchmark called without MAP_FIXED set");
84 return;
85 }
86
87 // Create the mmap that will be used for the fixed mmaps.
88 uint8_t* area = reinterpret_cast<uint8_t*>(
89 mmap(nullptr, area_size, params.prot, params.flags & ~MAP_FIXED, fd, 0));
90 if (area == MAP_FAILED) {
91 state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
92 return;
93 }
94
95 MmapBenchmark(state, params, fd, area + offs);
96
97 if (munmap(area, area_size) != 0) {
98 state.SkipWithError(android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
99 return;
100 }
101 }
102
MmapFileBenchmark(benchmark::State & state,const struct MmapParams & params,size_t area_size,size_t offs)103 static void MmapFileBenchmark(benchmark::State& state, const struct MmapParams& params,
104 size_t area_size, size_t offs) {
105 TemporaryFile tf;
106
107 if (tf.fd < 0) {
108 state.SkipWithError(
109 android::base::StringPrintf("failed to create a temporary file: %s", strerror(errno))
110 .c_str());
111 return;
112 }
113
114 if (area_size > 0 && ftruncate(tf.fd, area_size)) {
115 state.SkipWithError(
116 android::base::StringPrintf("ftruncate failed: %s", strerror(errno)).c_str());
117 return;
118 }
119
120 if (params.flags & MAP_FIXED) {
121 MmapFixedBenchmark(state, params, tf.fd, area_size, offs);
122 } else {
123 MmapBenchmark(state, params, tf.fd);
124 }
125 }
126
127 // anon mmap
BM_syscall_mmap_anon_rw(benchmark::State & state)128 static void BM_syscall_mmap_anon_rw(benchmark::State& state) {
129 struct MmapParams params = {
130 .prot = PROT_READ | PROT_WRITE,
131 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
132 .size = state.range(0),
133 };
134
135 MmapBenchmark(state, params, 0);
136 }
137 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_rw, "AT_ALL_PAGE_SIZES");
138
BM_syscall_mmap_anon_noreserve(benchmark::State & state)139 static void BM_syscall_mmap_anon_noreserve(benchmark::State& state) {
140 struct MmapParams params = {
141 .prot = PROT_NONE,
142 .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
143 .size = state.range(0),
144 };
145
146 MmapBenchmark(state, params, 0);
147 }
148 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_noreserve, "AT_ALL_PAGE_SIZES");
149
BM_syscall_mmap_anon_none(benchmark::State & state)150 static void BM_syscall_mmap_anon_none(benchmark::State& state) {
151 struct MmapParams params = {
152 .prot = PROT_NONE,
153 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
154 .size = state.range(0),
155 };
156
157 MmapBenchmark(state, params, 0);
158 }
159 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_none, "AT_ALL_PAGE_SIZES");
160
161 // anon fixed mmap
BM_syscall_mmap_anon_rw_fixed(benchmark::State & state)162 static void BM_syscall_mmap_anon_rw_fixed(benchmark::State& state) {
163 struct MmapParams params = {
164 .prot = PROT_READ | PROT_WRITE,
165 .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
166 .size = state.range(0),
167 };
168
169 MmapFixedBenchmark(state, params, -1, params.size, 0);
170 }
171 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_rw_fixed, "AT_ALL_PAGE_SIZES");
172
BM_syscall_mmap_anon_none_fixed(benchmark::State & state)173 static void BM_syscall_mmap_anon_none_fixed(benchmark::State& state) {
174 struct MmapParams params = {
175 .prot = PROT_NONE,
176 .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
177 .size = state.range(0),
178 };
179
180 MmapFixedBenchmark(state, params, -1, params.size, 0);
181 }
182 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_none_fixed, "AT_ALL_PAGE_SIZES");
183
184 // file mmap
BM_syscall_mmap_file_rd_priv(benchmark::State & state)185 static void BM_syscall_mmap_file_rd_priv(benchmark::State& state) {
186 struct MmapParams params = {
187 .prot = PROT_READ,
188 .flags = MAP_PRIVATE,
189 .size = state.range(0),
190 };
191
192 MmapFileBenchmark(state, params, params.size, 0);
193 }
194 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rd_priv, "AT_ALL_PAGE_SIZES");
195
BM_syscall_mmap_file_rw_shared(benchmark::State & state)196 static void BM_syscall_mmap_file_rw_shared(benchmark::State& state) {
197 struct MmapParams params = {
198 .prot = PROT_READ | PROT_WRITE,
199 .flags = MAP_SHARED,
200 .size = state.range(0),
201 };
202
203 MmapFileBenchmark(state, params, params.size, 0);
204 }
205 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_shared, "AT_ALL_PAGE_SIZES");
206
207 // file fixed mmap
BM_syscall_mmap_file_rw_priv_fixed_start(benchmark::State & state)208 static void BM_syscall_mmap_file_rw_priv_fixed_start(benchmark::State& state) {
209 struct MmapParams params = {
210 .prot = PROT_READ | PROT_WRITE,
211 .flags = MAP_PRIVATE | MAP_FIXED,
212 .size = state.range(0),
213 };
214
215 // allocate 3x area and map at the start
216 MmapFileBenchmark(state, params, params.size * 3, 0);
217 }
218 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_start, "AT_ALL_PAGE_SIZES");
219
BM_syscall_mmap_file_rw_priv_fixed_mid(benchmark::State & state)220 static void BM_syscall_mmap_file_rw_priv_fixed_mid(benchmark::State& state) {
221 struct MmapParams params = {
222 .prot = PROT_READ | PROT_WRITE,
223 .flags = MAP_PRIVATE | MAP_FIXED,
224 .size = state.range(0),
225 };
226
227 // allocate 3x area and map at the middle
228 MmapFileBenchmark(state, params, params.size * 3, params.size);
229 }
230 // mapping at sub-page size offset is not supported, so run only for AT_MULTI_PAGE_SIZES
231 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_mid, "AT_MULTI_PAGE_SIZES");
232
BM_syscall_mmap_file_rw_priv_fixed_end(benchmark::State & state)233 static void BM_syscall_mmap_file_rw_priv_fixed_end(benchmark::State& state) {
234 struct MmapParams params = {
235 .prot = PROT_READ | PROT_WRITE,
236 .flags = MAP_PRIVATE | MAP_FIXED,
237 .size = state.range(0),
238 };
239
240 // allocate 3x area and map at the end
241 MmapFileBenchmark(state, params, params.size * 3, params.size * 2);
242 }
243 // mapping at sub-page size offset is not supported, so run only for AT_MULTI_PAGE_SIZES
244 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_rw_priv_fixed_end, "AT_MULTI_PAGE_SIZES");
245
BM_syscall_mmap_anon_mmap_only(benchmark::State & state)246 static void BM_syscall_mmap_anon_mmap_only(benchmark::State& state) {
247 struct MmapParams params = {
248 .prot = PROT_READ | PROT_WRITE,
249 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
250 .size = state.range(0),
251 };
252 MmapBenchmarkImpl<kBenchmarkMmapOnly>(state, params, 0);
253 }
254 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mmap_only, "AT_MULTI_PAGE_SIZES");
255
BM_syscall_mmap_anon_munmap_only(benchmark::State & state)256 static void BM_syscall_mmap_anon_munmap_only(benchmark::State& state) {
257 struct MmapParams params = {
258 .prot = PROT_READ | PROT_WRITE,
259 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
260 .size = state.range(0),
261 };
262 MmapBenchmarkImpl<kBenchmarkMunmapOnly>(state, params, 0);
263 }
264 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_munmap_only, "AT_MULTI_PAGE_SIZES");
265
MadviseBenchmark(benchmark::State & state,const struct MmapParams & params,int madvise_flags)266 void MadviseBenchmark(benchmark::State& state, const struct MmapParams& params, int madvise_flags) {
267 void* addr = mmap(nullptr, params.size, params.prot, params.flags, 0, 0);
268 if (addr == MAP_FAILED) {
269 state.SkipWithError(android::base::StringPrintf("mmap failed: %s", strerror(errno)).c_str());
270 return;
271 }
272 for (auto _ : state) {
273 state.PauseTiming();
274 if (params.prot & PROT_WRITE) {
275 MakeAllocationResident(addr, params.size, page_sz);
276 }
277 state.ResumeTiming();
278
279 madvise(addr, params.size, madvise_flags);
280 }
281
282 if (munmap(addr, params.size) != 0) {
283 state.SkipWithError(android::base::StringPrintf("munmap failed: %s", strerror(errno)).c_str());
284 }
285 }
286
BM_syscall_mmap_anon_madvise_dontneed(benchmark::State & state)287 static void BM_syscall_mmap_anon_madvise_dontneed(benchmark::State& state) {
288 struct MmapParams params = {
289 .prot = PROT_READ | PROT_WRITE,
290 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
291 .size = state.range(0),
292 };
293 MadviseBenchmark(state, params, MADV_DONTNEED);
294 }
295 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_dontneed, "AT_MULTI_PAGE_SIZES");
296
BM_syscall_mmap_anon_madvise_pageout(benchmark::State & state)297 static void BM_syscall_mmap_anon_madvise_pageout(benchmark::State& state) {
298 struct MmapParams params = {
299 .prot = PROT_READ | PROT_WRITE,
300 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
301 .size = state.range(0),
302 };
303 MadviseBenchmark(state, params, MADV_PAGEOUT);
304 }
305 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_pageout, "AT_MULTI_PAGE_SIZES");
306
BM_syscall_mmap_anon_madvise_free(benchmark::State & state)307 static void BM_syscall_mmap_anon_madvise_free(benchmark::State& state) {
308 struct MmapParams params = {
309 .prot = PROT_READ | PROT_WRITE,
310 .flags = MAP_PRIVATE | MAP_ANONYMOUS,
311 .size = state.range(0),
312 };
313 MadviseBenchmark(state, params, MADV_FREE);
314 }
315 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_madvise_free, "AT_MULTI_PAGE_SIZES");
316
MprotectBenchmark(benchmark::State & state,const struct MprotectParams & params,void * addr)317 void MprotectBenchmark(benchmark::State& state, const struct MprotectParams& params, void* addr) {
318 for (auto _ : state) {
319 state.PauseTiming();
320 /*
321 * Guarantee that physical memory pages are allocated for this region to prevent
322 * segmentation fault when using mprotect to change permissions.
323 */
324 if (params.initial_prot & PROT_WRITE) {
325 MakeAllocationResident(addr, params.size, page_sz);
326 }
327 state.ResumeTiming();
328
329 if (mprotect(addr, params.size, params.mprotect_prot) != 0) {
330 state.SkipWithError(android::base::StringPrintf("mprotect failed: %m"));
331 break;
332 }
333
334 state.PauseTiming();
335 // Revert back to the original protection
336 int res = mprotect(addr, params.size, params.initial_prot);
337 state.ResumeTiming();
338 if (res != 0) {
339 state.SkipWithError(
340 android::base::StringPrintf("mprotect failed to revert to original prot: %m"));
341 break;
342 }
343 }
344 }
345
MprotectBenchmarkWithMmapAnon(benchmark::State & state,const struct MprotectParams & params)346 static void MprotectBenchmarkWithMmapAnon(benchmark::State& state,
347 const struct MprotectParams& params) {
348 void* addr = mmap(nullptr, params.size, params.initial_prot, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
349 if (addr == MAP_FAILED) {
350 state.SkipWithError(android::base::StringPrintf("mmap failed: %m"));
351 return;
352 }
353
354 MprotectBenchmark(state, params, addr);
355
356 if (munmap(addr, params.size) != 0)
357 state.SkipWithError(android::base::StringPrintf("munmap failed: %m"));
358 }
359
BM_syscall_mmap_anon_mprotect_rw_to_rd(benchmark::State & state)360 static void BM_syscall_mmap_anon_mprotect_rw_to_rd(benchmark::State& state) {
361 struct MprotectParams params = {
362 .initial_prot = PROT_READ | PROT_WRITE,
363 .mprotect_prot = PROT_READ,
364 .size = state.range(0),
365 };
366 MprotectBenchmarkWithMmapAnon(state, params);
367 }
368 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rw_to_rd, "AT_ALL_PAGE_SIZES");
369
BM_syscall_mmap_anon_mprotect_rw_to_none(benchmark::State & state)370 static void BM_syscall_mmap_anon_mprotect_rw_to_none(benchmark::State& state) {
371 struct MprotectParams params = {
372 .initial_prot = PROT_READ | PROT_WRITE,
373 .mprotect_prot = PROT_NONE,
374 .size = state.range(0),
375 };
376 MprotectBenchmarkWithMmapAnon(state, params);
377 }
378 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rw_to_none, "AT_ALL_PAGE_SIZES");
379
BM_syscall_mmap_anon_mprotect_rd_to_none(benchmark::State & state)380 static void BM_syscall_mmap_anon_mprotect_rd_to_none(benchmark::State& state) {
381 struct MprotectParams params = {
382 .initial_prot = PROT_READ,
383 .mprotect_prot = PROT_NONE,
384 .size = state.range(0),
385 };
386 MprotectBenchmarkWithMmapAnon(state, params);
387 }
388 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_anon_mprotect_rd_to_none, "AT_ALL_PAGE_SIZES");
389
MprotectBenchmarkWithMmapFile(benchmark::State & state,const struct MprotectParams & params)390 static void MprotectBenchmarkWithMmapFile(benchmark::State& state,
391 const struct MprotectParams& params) {
392 TemporaryFile tf;
393
394 if (tf.fd < 0) {
395 state.SkipWithError(android::base::StringPrintf("failed to create a temporary file: %m"));
396 return;
397 }
398
399 if (params.size > 0 && ftruncate(tf.fd, params.size)) {
400 state.SkipWithError(android::base::StringPrintf("ftruncate failed: %m"));
401 return;
402 }
403
404 void* addr = mmap(nullptr, params.size, params.initial_prot, MAP_PRIVATE, tf.fd, 0);
405 if (addr == MAP_FAILED) {
406 state.SkipWithError(android::base::StringPrintf("mmap failed: %m"));
407 return;
408 }
409
410 MprotectBenchmark(state, params, addr);
411
412 if (munmap(addr, params.size) != 0)
413 state.SkipWithError(android::base::StringPrintf("munmap failed: %m"));
414 }
415
BM_syscall_mmap_file_mprotect_rw_to_rd(benchmark::State & state)416 static void BM_syscall_mmap_file_mprotect_rw_to_rd(benchmark::State& state) {
417 struct MprotectParams params = {
418 .initial_prot = PROT_READ | PROT_WRITE,
419 .mprotect_prot = PROT_READ,
420 .size = state.range(0),
421 };
422 MprotectBenchmarkWithMmapFile(state, params);
423 }
424 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rw_to_rd, "AT_ALL_PAGE_SIZES");
425
BM_syscall_mmap_file_mprotect_rw_to_none(benchmark::State & state)426 static void BM_syscall_mmap_file_mprotect_rw_to_none(benchmark::State& state) {
427 struct MprotectParams params = {
428 .initial_prot = PROT_READ | PROT_WRITE,
429 .mprotect_prot = PROT_NONE,
430 .size = state.range(0),
431 };
432 MprotectBenchmarkWithMmapFile(state, params);
433 }
434 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rw_to_none, "AT_ALL_PAGE_SIZES");
435
BM_syscall_mmap_file_mprotect_none_to_rw(benchmark::State & state)436 static void BM_syscall_mmap_file_mprotect_none_to_rw(benchmark::State& state) {
437 struct MprotectParams params = {
438 .initial_prot = PROT_NONE,
439 .mprotect_prot = PROT_READ | PROT_WRITE,
440 .size = state.range(0),
441 };
442 MprotectBenchmarkWithMmapFile(state, params);
443 }
444 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_none_to_rw, "AT_ALL_PAGE_SIZES");
445
BM_syscall_mmap_file_mprotect_none_to_rd(benchmark::State & state)446 static void BM_syscall_mmap_file_mprotect_none_to_rd(benchmark::State& state) {
447 struct MprotectParams params = {
448 .initial_prot = PROT_NONE,
449 .mprotect_prot = PROT_READ,
450 .size = state.range(0),
451 };
452 MprotectBenchmarkWithMmapFile(state, params);
453 }
454 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_none_to_rd, "AT_ALL_PAGE_SIZES");
455
BM_syscall_mmap_file_mprotect_rd_to_none(benchmark::State & state)456 static void BM_syscall_mmap_file_mprotect_rd_to_none(benchmark::State& state) {
457 struct MprotectParams params = {
458 .initial_prot = PROT_READ,
459 .mprotect_prot = PROT_NONE,
460 .size = state.range(0),
461 };
462 MprotectBenchmarkWithMmapFile(state, params);
463 }
464 BIONIC_BENCHMARK_WITH_ARG(BM_syscall_mmap_file_mprotect_rd_to_none, "AT_ALL_PAGE_SIZES");
465