1 /*
2 * Copyright (C) 2019 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 <err.h>
18 #include <inttypes.h>
19 #include <malloc.h>
20 #include <sched.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include <algorithm>
28 #include <stack>
29 #include <string>
30 #include <unordered_map>
31 #include <vector>
32
33 #include <android-base/file.h>
34 #include <android-base/strings.h>
35 #include <benchmark/benchmark.h>
36
37 #include "Alloc.h"
38 #include "Utils.h"
39 #include "Zip.h"
40
41 struct TraceAllocEntry {
TraceAllocEntryTraceAllocEntry42 TraceAllocEntry(AllocEnum type, size_t idx, size_t size, size_t last_arg)
43 : type(type), idx(idx), size(size) {
44 u.old_idx = last_arg;
45 }
46 AllocEnum type;
47 size_t idx;
48 size_t size;
49 union {
50 size_t old_idx = 0;
51 size_t align;
52 size_t n_elements;
53 } u;
54 };
55
GetIndex(std::stack<size_t> & free_indices,size_t * max_index)56 static size_t GetIndex(std::stack<size_t>& free_indices, size_t* max_index) {
57 if (free_indices.empty()) {
58 return (*max_index)++;
59 }
60 size_t index = free_indices.top();
61 free_indices.pop();
62 return index;
63 }
64
GetTraceData(const char * filename,size_t * max_ptrs)65 static std::vector<TraceAllocEntry>* GetTraceData(const char* filename, size_t* max_ptrs) {
66 // Only keep last trace encountered cached.
67 static std::string cached_filename;
68 static std::vector<TraceAllocEntry> cached_entries;
69 static size_t cached_max_ptrs;
70
71 if (cached_filename == filename) {
72 *max_ptrs = cached_max_ptrs;
73 return &cached_entries;
74 }
75
76 cached_entries.clear();
77 cached_max_ptrs = 0;
78 cached_filename = filename;
79
80 std::string content(ZipGetContents(filename));
81 if (content.empty()) {
82 errx(1, "Internal Error: Empty zip file %s", filename);
83 }
84 std::vector<std::string> lines(android::base::Split(content, "\n"));
85
86 *max_ptrs = 0;
87 std::stack<size_t> free_indices;
88 std::unordered_map<uint64_t, size_t> ptr_to_index;
89 std::vector<TraceAllocEntry>* entries = &cached_entries;
90 for (const std::string& line : lines) {
91 if (line.empty()) {
92 continue;
93 }
94 AllocEntry entry;
95 AllocGetData(line, &entry);
96
97 switch (entry.type) {
98 case MALLOC: {
99 size_t idx = GetIndex(free_indices, max_ptrs);
100 ptr_to_index[entry.ptr] = idx;
101 entries->emplace_back(MALLOC, idx, entry.size, 0);
102 break;
103 }
104 case CALLOC: {
105 size_t idx = GetIndex(free_indices, max_ptrs);
106 ptr_to_index[entry.ptr] = idx;
107 entries->emplace_back(CALLOC, idx, entry.u.n_elements, entry.size);
108 break;
109 }
110 case MEMALIGN: {
111 size_t idx = GetIndex(free_indices, max_ptrs);
112 ptr_to_index[entry.ptr] = idx;
113 entries->emplace_back(MEMALIGN, idx, entry.size, entry.u.align);
114 break;
115 }
116 case REALLOC: {
117 size_t old_pointer_idx = 0;
118 if (entry.u.old_ptr != 0) {
119 auto idx_entry = ptr_to_index.find(entry.u.old_ptr);
120 if (idx_entry == ptr_to_index.end()) {
121 errx(1, "File Error: Failed to find realloc pointer %" PRIu64, entry.u.old_ptr);
122 }
123 old_pointer_idx = idx_entry->second;
124 free_indices.push(old_pointer_idx);
125 }
126 size_t idx = GetIndex(free_indices, max_ptrs);
127 ptr_to_index[entry.ptr] = idx;
128 entries->emplace_back(REALLOC, idx, entry.size, old_pointer_idx + 1);
129 break;
130 }
131 case FREE:
132 if (entry.ptr != 0) {
133 auto idx_entry = ptr_to_index.find(entry.ptr);
134 if (idx_entry == ptr_to_index.end()) {
135 errx(1, "File Error: Unable to find free pointer %" PRIu64, entry.ptr);
136 }
137 free_indices.push(idx_entry->second);
138 entries->emplace_back(FREE, idx_entry->second + 1, 0, 0);
139 } else {
140 entries->emplace_back(FREE, 0, 0, 0);
141 }
142 break;
143 case THREAD_DONE:
144 // Ignore these.
145 break;
146 }
147 }
148
149 cached_max_ptrs = *max_ptrs;
150 return entries;
151 }
152
RunTrace(benchmark::State & state,std::vector<TraceAllocEntry> & entries,size_t max_ptrs)153 static void RunTrace(benchmark::State& state, std::vector<TraceAllocEntry>& entries,
154 size_t max_ptrs) {
155 std::vector<void*> ptrs(max_ptrs, nullptr);
156
157 int pagesize = getpagesize();
158 void* ptr;
159 uint64_t total_ns = 0;
160 uint64_t start_ns;
161 for (auto& entry : entries) {
162 switch (entry.type) {
163 case MALLOC:
164 start_ns = Nanotime();
165 ptr = malloc(entry.size);
166 if (ptr == nullptr) {
167 errx(1, "malloc returned nullptr");
168 }
169 MakeAllocationResident(ptr, entry.size, pagesize);
170 total_ns += Nanotime() - start_ns;
171
172 if (ptrs[entry.idx] != nullptr) {
173 errx(1, "Internal Error: malloc pointer being replaced is not nullptr");
174 }
175 ptrs[entry.idx] = ptr;
176 break;
177
178 case CALLOC:
179 start_ns = Nanotime();
180 ptr = calloc(entry.u.n_elements, entry.size);
181 if (ptr == nullptr) {
182 errx(1, "calloc returned nullptr");
183 }
184 MakeAllocationResident(ptr, entry.size, pagesize);
185 total_ns += Nanotime() - start_ns;
186
187 if (ptrs[entry.idx] != nullptr) {
188 errx(1, "Internal Error: calloc pointer being replaced is not nullptr");
189 }
190 ptrs[entry.idx] = ptr;
191 break;
192
193 case MEMALIGN:
194 start_ns = Nanotime();
195 ptr = memalign(entry.u.align, entry.size);
196 if (ptr == nullptr) {
197 errx(1, "memalign returned nullptr");
198 }
199 MakeAllocationResident(ptr, entry.size, pagesize);
200 total_ns += Nanotime() - start_ns;
201
202 if (ptrs[entry.idx] != nullptr) {
203 errx(1, "Internal Error: memalign pointer being replaced is not nullptr");
204 }
205 ptrs[entry.idx] = ptr;
206 break;
207
208 case REALLOC:
209 start_ns = Nanotime();
210 if (entry.u.old_idx == 0) {
211 ptr = realloc(nullptr, entry.size);
212 } else {
213 ptr = realloc(ptrs[entry.u.old_idx - 1], entry.size);
214 ptrs[entry.u.old_idx - 1] = nullptr;
215 }
216 if (entry.size > 0) {
217 if (ptr == nullptr) {
218 errx(1, "realloc returned nullptr");
219 }
220 MakeAllocationResident(ptr, entry.size, pagesize);
221 }
222 total_ns += Nanotime() - start_ns;
223
224 if (ptrs[entry.idx] != nullptr) {
225 errx(1, "Internal Error: realloc pointer being replaced is not nullptr");
226 }
227 ptrs[entry.idx] = ptr;
228 break;
229
230 case FREE:
231 if (entry.idx != 0) {
232 ptr = ptrs[entry.idx - 1];
233 ptrs[entry.idx - 1] = nullptr;
234 } else {
235 ptr = nullptr;
236 }
237 start_ns = Nanotime();
238 free(ptr);
239 total_ns += Nanotime() - start_ns;
240 break;
241
242 case THREAD_DONE:
243 break;
244 }
245 }
246 state.SetIterationTime(total_ns / double(1000000000.0));
247
248 std::for_each(ptrs.begin(), ptrs.end(), [](void* ptr) { free(ptr); });
249 }
250
251 // Run a trace as if all of the allocations occurred in a single thread.
252 // This is not completely realistic, but it is a possible worst case that
253 // could happen in an app.
BenchmarkTrace(benchmark::State & state,const char * filename)254 static void BenchmarkTrace(benchmark::State& state, const char* filename) {
255 std::string full_filename(android::base::GetExecutableDirectory() + "/traces/" + filename);
256 size_t max_ptrs;
257 std::vector<TraceAllocEntry>* entries = GetTraceData(full_filename.c_str(), &max_ptrs);
258 if (entries == nullptr) {
259 errx(1, "ERROR: Failed to get trace data for %s.", full_filename.c_str());
260 }
261
262 #if defined(__BIONIC__)
263 // Need to set the decay time the same as how an app would operate.
264 mallopt(M_DECAY_TIME, 1);
265 #endif
266
267 for (auto _ : state) {
268 RunTrace(state, *entries, max_ptrs);
269 }
270 }
271
272 #define BENCH_OPTIONS \
273 UseManualTime() \
274 ->Unit(benchmark::kMicrosecond) \
275 ->MinTime(15.0) \
276 ->Repetitions(4) \
277 ->ReportAggregatesOnly(true)
278
BM_angry_birds2(benchmark::State & state)279 static void BM_angry_birds2(benchmark::State& state) {
280 BenchmarkTrace(state, "angry_birds2.zip");
281 }
282 BENCHMARK(BM_angry_birds2)->BENCH_OPTIONS;
283
BM_camera(benchmark::State & state)284 static void BM_camera(benchmark::State& state) {
285 BenchmarkTrace(state, "camera.zip");
286 }
287 BENCHMARK(BM_camera)->BENCH_OPTIONS;
288
BM_candy_crush_saga(benchmark::State & state)289 static void BM_candy_crush_saga(benchmark::State& state) {
290 BenchmarkTrace(state, "candy_crush_saga.zip");
291 }
292 BENCHMARK(BM_candy_crush_saga)->BENCH_OPTIONS;
293
BM_gmail(benchmark::State & state)294 void BM_gmail(benchmark::State& state) {
295 BenchmarkTrace(state, "gmail.zip");
296 }
297 BENCHMARK(BM_gmail)->BENCH_OPTIONS;
298
BM_maps(benchmark::State & state)299 void BM_maps(benchmark::State& state) {
300 BenchmarkTrace(state, "maps.zip");
301 }
302 BENCHMARK(BM_maps)->BENCH_OPTIONS;
303
BM_photos(benchmark::State & state)304 void BM_photos(benchmark::State& state) {
305 BenchmarkTrace(state, "photos.zip");
306 }
307 BENCHMARK(BM_photos)->BENCH_OPTIONS;
308
BM_pubg(benchmark::State & state)309 void BM_pubg(benchmark::State& state) {
310 BenchmarkTrace(state, "pubg.zip");
311 }
312 BENCHMARK(BM_pubg)->BENCH_OPTIONS;
313
BM_surfaceflinger(benchmark::State & state)314 void BM_surfaceflinger(benchmark::State& state) {
315 BenchmarkTrace(state, "surfaceflinger.zip");
316 }
317 BENCHMARK(BM_surfaceflinger)->BENCH_OPTIONS;
318
BM_system_server(benchmark::State & state)319 void BM_system_server(benchmark::State& state) {
320 BenchmarkTrace(state, "system_server.zip");
321 }
322 BENCHMARK(BM_system_server)->BENCH_OPTIONS;
323
BM_systemui(benchmark::State & state)324 void BM_systemui(benchmark::State& state) {
325 BenchmarkTrace(state, "systemui.zip");
326 }
327 BENCHMARK(BM_systemui)->BENCH_OPTIONS;
328
BM_youtube(benchmark::State & state)329 void BM_youtube(benchmark::State& state) {
330 BenchmarkTrace(state, "youtube.zip");
331 }
332 BENCHMARK(BM_youtube)->BENCH_OPTIONS;
333
main(int argc,char ** argv)334 int main(int argc, char** argv) {
335 std::vector<char*> args;
336 args.push_back(argv[0]);
337
338 // Look for the --cpu=XX option.
339 for (int i = 1; i < argc; i++) {
340 if (strncmp(argv[i], "--cpu=", 6) == 0) {
341 char* endptr;
342 int cpu = strtol(&argv[i][6], &endptr, 10);
343 if (argv[i][0] == '\0' || endptr == nullptr || *endptr != '\0') {
344 printf("Invalid format of --cpu option, '%s' must be an integer value.\n", argv[i] + 6);
345 return 1;
346 }
347 cpu_set_t cpuset;
348 CPU_ZERO(&cpuset);
349 CPU_SET(cpu, &cpuset);
350 if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
351 if (errno == EINVAL) {
352 printf("Invalid cpu %d\n", cpu);
353 return 1;
354 }
355 perror("sched_setaffinity failed");
356 return 1;
357 }
358 printf("Locking to cpu %d\n", cpu);
359 } else {
360 args.push_back(argv[i]);
361 }
362 }
363
364 argc = args.size();
365 ::benchmark::Initialize(&argc, args.data());
366 if (::benchmark::ReportUnrecognizedArguments(argc, args.data())) return 1;
367 ::benchmark::RunSpecifiedBenchmarks();
368 }
369