Merge "Add a malloc rss benchmark"
diff --git a/benchmarks/Android.bp b/benchmarks/Android.bp
index 5dfc38f..17d2d68 100644
--- a/benchmarks/Android.bp
+++ b/benchmarks/Android.bp
@@ -156,3 +156,23 @@
],
data: ["test_suites/*"],
}
+
+cc_binary {
+ name: "malloc-rss-benchmark",
+ srcs: [
+ "malloc_rss_benchmark.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ ],
+
+ target: {
+ android: {
+ static_libs: [
+ "libmeminfo",
+ "libprocinfo",
+ ],
+ },
+ },
+}
diff --git a/benchmarks/NOTICE b/benchmarks/NOTICE
index f720e23..e46a624 100644
--- a/benchmarks/NOTICE
+++ b/benchmarks/NOTICE
@@ -178,3 +178,31 @@
-------------------------------------------------------------------
+Copyright (C) 2022 The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
diff --git a/benchmarks/malloc_rss_benchmark.cpp b/benchmarks/malloc_rss_benchmark.cpp
new file mode 100644
index 0000000..58f61d9
--- /dev/null
+++ b/benchmarks/malloc_rss_benchmark.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <random>
+#include <thread>
+#include <vector>
+
+#include <android-base/strings.h>
+#if defined(__BIONIC__)
+#include <malloc.h>
+#include <meminfo/procmeminfo.h>
+#include <procinfo/process_map.h>
+#endif
+
+constexpr size_t kMaxThreads = 8;
+// The max number of bytes that can be allocated by a thread. Note that each
+// allocator may have its own limitation on each size allocation. For example,
+// Scudo has a 256 MB limit for each size-class in the primary allocator. The
+// amount of memory allocated should not exceed the limit in each allocator.
+constexpr size_t kMaxBytes = 1 << 24;
+constexpr size_t kMaxLen = kMaxBytes;
+void* MemPool[kMaxThreads][kMaxLen];
+
+void dirtyMem(void* ptr, size_t bytes) {
+ memset(ptr, 1U, bytes);
+}
+
+void ThreadTask(int id, size_t allocSize) {
+ // In the following, we will first allocate blocks with kMaxBytes of memory
+ // and release all of them in random order. In the end, we will do another
+ // round of allocations until it reaches 1/10 kMaxBytes.
+
+ // Total number of blocks
+ const size_t maxCounts = kMaxBytes / allocSize;
+ // The number of blocks in the end
+ const size_t finalCounts = maxCounts / 10;
+
+ for (size_t i = 0; i < maxCounts; ++i) {
+ MemPool[id][i] = malloc(allocSize);
+ if (MemPool[id][i] == 0) {
+ std::cout << "Allocation failure."
+ "Please consider reducing the number of threads"
+ << std::endl;
+ exit(1);
+ }
+ dirtyMem(MemPool[id][i], allocSize);
+ }
+
+ // Each allocator may apply different strategies to manage the free blocks and
+ // each strategy may have different impacts on future memory usage. For
+ // example, managing free blocks in simple FIFO list may have its memory usage
+ // highly correlated with the blocks releasing pattern. Therefore, release the
+ // blocks in random order to observe the impact of free blocks handling.
+ unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
+ std::shuffle(MemPool[id], MemPool[id] + maxCounts, std::default_random_engine(seed));
+ for (size_t i = 0; i < maxCounts; ++i) {
+ free(MemPool[id][i]);
+ MemPool[id][i] = nullptr;
+ }
+
+ for (size_t i = 0; i < finalCounts; ++i) {
+ MemPool[id][i] = malloc(allocSize);
+ dirtyMem(MemPool[id][i], allocSize);
+ }
+}
+
+void StressSizeClass(size_t numThreads, size_t allocSize) {
+ // We would like to see the minimum memory usage under aggressive page
+ // releasing.
+ mallopt(M_DECAY_TIME, 0);
+
+ std::thread* threads[kMaxThreads];
+ for (size_t i = 0; i < numThreads; ++i) threads[i] = new std::thread(ThreadTask, i, allocSize);
+
+ for (size_t i = 0; i < numThreads; ++i) {
+ threads[i]->join();
+ delete threads[i];
+ }
+
+ // Do an explicit purge to ensure we will be more likely to get the actual
+ // in-use memory.
+ mallopt(M_PURGE, 0);
+
+ android::meminfo::ProcMemInfo proc_mem(getpid());
+ const std::vector<android::meminfo::Vma>& maps = proc_mem.MapsWithoutUsageStats();
+ uint64_t rss_bytes = 0;
+ uint64_t vss_bytes = 0;
+
+ for (auto& vma : maps) {
+ if (vma.name == "[anon:libc_malloc]" || android::base::StartsWith(vma.name, "[anon:scudo:") ||
+ android::base::StartsWith(vma.name, "[anon:GWP-ASan")) {
+ android::meminfo::Vma update_vma(vma);
+ if (!proc_mem.FillInVmaStats(update_vma)) {
+ std::cout << "Failed to parse VMA" << std::endl;
+ exit(1);
+ }
+ rss_bytes += update_vma.usage.rss;
+ vss_bytes += update_vma.usage.vss;
+ }
+ }
+
+ std::cout << "RSS: " << rss_bytes / (1024.0 * 1024.0) << " MB" << std::endl;
+ std::cout << "VSS: " << vss_bytes / (1024.0 * 1024.0) << " MB" << std::endl;
+
+ for (size_t i = 0; i < numThreads; ++i) {
+ for (size_t j = 0; j < kMaxLen; ++j) free(MemPool[i][j]);
+ }
+}
+
+int main(int argc, char* argv[]) {
+ if (argc != 3) {
+ std::cerr << "usage: " << argv[0] << " $NUM_THREADS $ALLOC_SIZE" << std::endl;
+ return 1;
+ }
+
+ size_t numThreads = atoi(argv[1]);
+ size_t allocSize = atoi(argv[2]);
+
+ if (numThreads == 0 || allocSize == 0) {
+ std::cerr << "Please provide valid $NUM_THREADS and $ALLOC_SIZE" << std::endl;
+ return 1;
+ }
+
+ if (numThreads > kMaxThreads) {
+ std::cerr << "The max number of threads is " << kMaxThreads << std::endl;
+ return 1;
+ }
+
+ StressSizeClass(numThreads, allocSize);
+
+ return 0;
+}