| /* | |
| * Copyright (C) 2019 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <err.h> | |
| #include <inttypes.h> | |
| #include <malloc.h> | |
| #include <sched.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| #include <algorithm> | |
| #include <stack> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <vector> | |
| #include <android-base/file.h> | |
| #include <android-base/strings.h> | |
| #include <benchmark/benchmark.h> | |
| #include "Alloc.h" | |
| #include "File.h" | |
| #include "Utils.h" | |
| struct TraceDataType { | |
| AllocEntry* entries = nullptr; | |
| size_t num_entries = 0; | |
| void** ptrs = nullptr; | |
| size_t num_ptrs = 0; | |
| }; | |
| static size_t GetIndex(std::stack<size_t>& free_indices, size_t* max_index) { | |
| if (free_indices.empty()) { | |
| return (*max_index)++; | |
| } | |
| size_t index = free_indices.top(); | |
| free_indices.pop(); | |
| return index; | |
| } | |
| static void FreePtrs(TraceDataType* trace_data) { | |
| for (size_t i = 0; i < trace_data->num_ptrs; i++) { | |
| void* ptr = trace_data->ptrs[i]; | |
| if (ptr != nullptr) { | |
| free(ptr); | |
| trace_data->ptrs[i] = nullptr; | |
| } | |
| } | |
| } | |
| static void FreeTraceData(TraceDataType* trace_data) { | |
| if (trace_data->ptrs == nullptr) { | |
| return; | |
| } | |
| munmap(trace_data->ptrs, sizeof(void*) * trace_data->num_ptrs); | |
| FreeEntries(trace_data->entries, trace_data->num_entries); | |
| } | |
| static void GetTraceData(const std::string& filename, TraceDataType* trace_data) { | |
| // Only keep last trace encountered cached. | |
| static std::string cached_filename; | |
| static TraceDataType cached_trace_data; | |
| if (cached_filename == filename) { | |
| *trace_data = cached_trace_data; | |
| return; | |
| } else { | |
| FreeTraceData(&cached_trace_data); | |
| } | |
| cached_filename = filename; | |
| GetUnwindInfo(filename.c_str(), &trace_data->entries, &trace_data->num_entries); | |
| // This loop will convert the ptr field into an index into the ptrs array. | |
| // Creating this index allows the trace run to quickly store or retrieve the | |
| // allocation. | |
| // For free, the ptr field will be index + one, where a zero represents | |
| // a free(nullptr) call. | |
| // For realloc, the old_pointer field will be index + one, where a zero | |
| // represents a realloc(nullptr, XX). | |
| trace_data->num_ptrs = 0; | |
| std::stack<size_t> free_indices; | |
| std::unordered_map<uint64_t, size_t> ptr_to_index; | |
| for (size_t i = 0; i < trace_data->num_entries; i++) { | |
| AllocEntry* entry = &trace_data->entries[i]; | |
| switch (entry->type) { | |
| case MALLOC: | |
| case CALLOC: | |
| case MEMALIGN: { | |
| size_t idx = GetIndex(free_indices, &trace_data->num_ptrs); | |
| ptr_to_index[entry->ptr] = idx; | |
| entry->ptr = idx; | |
| break; | |
| } | |
| case REALLOC: { | |
| if (entry->u.old_ptr != 0) { | |
| auto idx_entry = ptr_to_index.find(entry->u.old_ptr); | |
| if (idx_entry == ptr_to_index.end()) { | |
| errx(1, "File Error: Failed to find realloc pointer %" PRIx64, entry->u.old_ptr); | |
| } | |
| size_t old_pointer_idx = idx_entry->second; | |
| free_indices.push(old_pointer_idx); | |
| ptr_to_index.erase(idx_entry); | |
| entry->u.old_ptr = old_pointer_idx + 1; | |
| } | |
| size_t idx = GetIndex(free_indices, &trace_data->num_ptrs); | |
| ptr_to_index[entry->ptr] = idx; | |
| entry->ptr = idx; | |
| break; | |
| } | |
| case FREE: | |
| if (entry->ptr != 0) { | |
| auto idx_entry = ptr_to_index.find(entry->ptr); | |
| if (idx_entry == ptr_to_index.end()) { | |
| errx(1, "File Error: Unable to find free pointer %" PRIx64, entry->ptr); | |
| } | |
| free_indices.push(idx_entry->second); | |
| entry->ptr = idx_entry->second + 1; | |
| ptr_to_index.erase(idx_entry); | |
| } | |
| break; | |
| case THREAD_DONE: | |
| break; | |
| } | |
| } | |
| void* map = mmap(nullptr, sizeof(void*) * trace_data->num_ptrs, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); | |
| if (map == MAP_FAILED) { | |
| err(1, "mmap failed\n"); | |
| } | |
| trace_data->ptrs = reinterpret_cast<void**>(map); | |
| cached_trace_data = *trace_data; | |
| } | |
| static void RunTrace(benchmark::State& state, TraceDataType* trace_data) { | |
| int pagesize = getpagesize(); | |
| uint64_t total_ns = 0; | |
| uint64_t start_ns; | |
| void** ptrs = trace_data->ptrs; | |
| for (size_t i = 0; i < trace_data->num_entries; i++) { | |
| void* ptr; | |
| const AllocEntry& entry = trace_data->entries[i]; | |
| switch (entry.type) { | |
| case MALLOC: | |
| start_ns = Nanotime(); | |
| ptr = malloc(entry.size); | |
| if (ptr == nullptr) { | |
| errx(1, "malloc returned nullptr"); | |
| } | |
| MakeAllocationResident(ptr, entry.size, pagesize); | |
| total_ns += Nanotime() - start_ns; | |
| if (ptrs[entry.ptr] != nullptr) { | |
| errx(1, "Internal Error: malloc pointer being replaced is not nullptr"); | |
| } | |
| ptrs[entry.ptr] = ptr; | |
| break; | |
| case CALLOC: | |
| start_ns = Nanotime(); | |
| ptr = calloc(entry.u.n_elements, entry.size); | |
| if (ptr == nullptr) { | |
| errx(1, "calloc returned nullptr"); | |
| } | |
| MakeAllocationResident(ptr, entry.size, pagesize); | |
| total_ns += Nanotime() - start_ns; | |
| if (ptrs[entry.ptr] != nullptr) { | |
| errx(1, "Internal Error: calloc pointer being replaced is not nullptr"); | |
| } | |
| ptrs[entry.ptr] = ptr; | |
| break; | |
| case MEMALIGN: | |
| start_ns = Nanotime(); | |
| ptr = memalign(entry.u.align, entry.size); | |
| if (ptr == nullptr) { | |
| errx(1, "memalign returned nullptr"); | |
| } | |
| MakeAllocationResident(ptr, entry.size, pagesize); | |
| total_ns += Nanotime() - start_ns; | |
| if (ptrs[entry.ptr] != nullptr) { | |
| errx(1, "Internal Error: memalign pointer being replaced is not nullptr"); | |
| } | |
| ptrs[entry.ptr] = ptr; | |
| break; | |
| case REALLOC: | |
| start_ns = Nanotime(); | |
| if (entry.u.old_ptr == 0) { | |
| ptr = realloc(nullptr, entry.size); | |
| } else { | |
| ptr = realloc(ptrs[entry.u.old_ptr - 1], entry.size); | |
| ptrs[entry.u.old_ptr - 1] = nullptr; | |
| } | |
| if (entry.size > 0) { | |
| if (ptr == nullptr) { | |
| errx(1, "realloc returned nullptr"); | |
| } | |
| MakeAllocationResident(ptr, entry.size, pagesize); | |
| } | |
| total_ns += Nanotime() - start_ns; | |
| if (ptrs[entry.ptr] != nullptr) { | |
| errx(1, "Internal Error: realloc pointer being replaced is not nullptr"); | |
| } | |
| ptrs[entry.ptr] = ptr; | |
| break; | |
| case FREE: | |
| if (entry.ptr != 0) { | |
| ptr = ptrs[entry.ptr - 1]; | |
| ptrs[entry.ptr - 1] = nullptr; | |
| } else { | |
| ptr = nullptr; | |
| } | |
| start_ns = Nanotime(); | |
| free(ptr); | |
| total_ns += Nanotime() - start_ns; | |
| break; | |
| case THREAD_DONE: | |
| break; | |
| } | |
| } | |
| state.SetIterationTime(total_ns / double(1000000000.0)); | |
| FreePtrs(trace_data); | |
| } | |
| // Run a trace as if all of the allocations occurred in a single thread. | |
| // This is not completely realistic, but it is a possible worst case that | |
| // could happen in an app. | |
| static void BenchmarkTrace(benchmark::State& state, const char* filename, bool enable_decay_time) { | |
| #if defined(__BIONIC__) | |
| if (enable_decay_time) { | |
| mallopt(M_DECAY_TIME, 1); | |
| } else { | |
| mallopt(M_DECAY_TIME, 0); | |
| } | |
| #endif | |
| std::string full_filename(android::base::GetExecutableDirectory() + "/traces/" + filename); | |
| TraceDataType trace_data; | |
| GetTraceData(full_filename, &trace_data); | |
| for (auto _ : state) { | |
| RunTrace(state, &trace_data); | |
| } | |
| // Don't free the trace_data, it is cached. The last set of trace data | |
| // will be leaked away. | |
| } | |
| #define BENCH_OPTIONS \ | |
| UseManualTime() \ | |
| ->Unit(benchmark::kMicrosecond) \ | |
| ->MinTime(15.0) \ | |
| ->Repetitions(4) \ | |
| ->ReportAggregatesOnly(true) | |
| static void BM_angry_birds2(benchmark::State& state) { | |
| BenchmarkTrace(state, "angry_birds2.zip", true); | |
| } | |
| BENCHMARK(BM_angry_birds2)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| static void BM_angry_birds2_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "angry_birds2.zip", false); | |
| } | |
| BENCHMARK(BM_angry_birds2_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| static void BM_camera(benchmark::State& state) { | |
| BenchmarkTrace(state, "camera.zip", true); | |
| } | |
| BENCHMARK(BM_camera)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| static void BM_camera_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "camera.zip", false); | |
| } | |
| BENCHMARK(BM_camera_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| static void BM_candy_crush_saga(benchmark::State& state) { | |
| BenchmarkTrace(state, "candy_crush_saga.zip", true); | |
| } | |
| BENCHMARK(BM_candy_crush_saga)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| static void BM_candy_crush_saga_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "candy_crush_saga.zip", false); | |
| } | |
| BENCHMARK(BM_candy_crush_saga_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_gmail(benchmark::State& state) { | |
| BenchmarkTrace(state, "gmail.zip", true); | |
| } | |
| BENCHMARK(BM_gmail)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_gmail_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "gmail.zip", false); | |
| } | |
| BENCHMARK(BM_gmail_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_maps(benchmark::State& state) { | |
| BenchmarkTrace(state, "maps.zip", true); | |
| } | |
| BENCHMARK(BM_maps)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_maps_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "maps.zip", false); | |
| } | |
| BENCHMARK(BM_maps_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_photos(benchmark::State& state) { | |
| BenchmarkTrace(state, "photos.zip", true); | |
| } | |
| BENCHMARK(BM_photos)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_photos_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "photos.zip", false); | |
| } | |
| BENCHMARK(BM_photos_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_pubg(benchmark::State& state) { | |
| BenchmarkTrace(state, "pubg.zip", true); | |
| } | |
| BENCHMARK(BM_pubg)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_pubg_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "pubg.zip", false); | |
| } | |
| BENCHMARK(BM_pubg_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_surfaceflinger(benchmark::State& state) { | |
| BenchmarkTrace(state, "surfaceflinger.zip", true); | |
| } | |
| BENCHMARK(BM_surfaceflinger)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_surfaceflinger_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "surfaceflinger.zip", false); | |
| } | |
| BENCHMARK(BM_surfaceflinger_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_system_server(benchmark::State& state) { | |
| BenchmarkTrace(state, "system_server.zip", true); | |
| } | |
| BENCHMARK(BM_system_server)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_system_server_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "system_server.zip", false); | |
| } | |
| BENCHMARK(BM_system_server_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_systemui(benchmark::State& state) { | |
| BenchmarkTrace(state, "systemui.zip", true); | |
| } | |
| BENCHMARK(BM_systemui)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_systemui_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "systemui.zip", false); | |
| } | |
| BENCHMARK(BM_systemui_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| void BM_youtube(benchmark::State& state) { | |
| BenchmarkTrace(state, "youtube.zip", true); | |
| } | |
| BENCHMARK(BM_youtube)->BENCH_OPTIONS; | |
| #if defined(__BIONIC__) | |
| void BM_youtube_no_decay(benchmark::State& state) { | |
| BenchmarkTrace(state, "youtube.zip", false); | |
| } | |
| BENCHMARK(BM_youtube_no_decay)->BENCH_OPTIONS; | |
| #endif | |
| int main(int argc, char** argv) { | |
| std::vector<char*> args; | |
| args.push_back(argv[0]); | |
| // Look for the --cpu=XX option. | |
| for (int i = 1; i < argc; i++) { | |
| if (strncmp(argv[i], "--cpu=", 6) == 0) { | |
| char* endptr; | |
| int cpu = strtol(&argv[i][6], &endptr, 10); | |
| if (argv[i][0] == '\0' || endptr == nullptr || *endptr != '\0') { | |
| printf("Invalid format of --cpu option, '%s' must be an integer value.\n", argv[i] + 6); | |
| return 1; | |
| } | |
| cpu_set_t cpuset; | |
| CPU_ZERO(&cpuset); | |
| CPU_SET(cpu, &cpuset); | |
| if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) { | |
| if (errno == EINVAL) { | |
| printf("Invalid cpu %d\n", cpu); | |
| return 1; | |
| } | |
| perror("sched_setaffinity failed"); | |
| return 1; | |
| } | |
| printf("Locking to cpu %d\n", cpu); | |
| } else { | |
| args.push_back(argv[i]); | |
| } | |
| } | |
| argc = args.size(); | |
| ::benchmark::Initialize(&argc, args.data()); | |
| if (::benchmark::ReportUnrecognizedArguments(argc, args.data())) return 1; | |
| ::benchmark::RunSpecifiedBenchmarks(); | |
| } |