Loading...
--- /dev/null
+++ libmalloc/libmalloc-521.100.59/tests/basic_malloc_free_perf.c
@@ -0,0 +1,445 @@
+//
+// basic_malloc_free_perf.c
+// libmalloc
+//
+// Simple and repeatable performance tests for malloc/free, running tests
+// on the regular and Nanov2 allocators.
+//
+#include <darwintest.h>
+#include <dispatch/dispatch.h>
+#include <../src/internal.h>
+#include <perfcheck_keys.h>
+
+// This value is a guess that will be refined over time.
+#define PERFCHECK_THRESHOLD_PCT 10.0
+
+static uint32_t
+ncpu(void)
+{
+ static uint32_t activecpu, physicalcpu;
+ if (!activecpu) {
+ uint32_t n;
+ size_t s = sizeof(n);
+ sysctlbyname("hw.activecpu", &n, &s, NULL, 0);
+ activecpu = n;
+ s = sizeof(n);
+ sysctlbyname("hw.physicalcpu", &n, &s, NULL, 0);
+ physicalcpu = n;
+ }
+ return MIN(activecpu, physicalcpu);
+}
+
+// Code to run a single test and save the converged sample time as the DPS
+// metric. The code also measures the time taken in dispatch_apply(), but that
+// should be more or less constant in all cases. We are only interested in
+// whether the overall sampled time regresses, not in the absolute time value.
+
+static void
+run_test(void (^test)(size_t), bool singlethreaded)
+{
+ uint32_t nthreads = 0;
+ if (singlethreaded) {
+ nthreads = 1;
+ } else {
+ const char *e;
+ if ((e = getenv("DT_STAT_NTHREADS"))) {
+ nthreads = strtoul(e, NULL, 0);
+ }
+ if (nthreads < 2) {
+ nthreads = ncpu();
+ }
+ }
+ dt_stat_time_t s = dt_stat_time_create(
+ nthreads > 1 ? "basic_malloc_free_perf multithreaded" :
+ "basic_malloc_free_perf singlethreaded");
+ dt_stat_set_variable((dt_stat_t)s, "threads", nthreads);
+ do {
+ int batch_size = dt_stat_batch_size(s);
+ dt_stat_token t = dt_stat_begin(s);
+ // rdar://problem/40417821: disable thresholds for now.
+ //dt_stat_set_variable((dt_stat_t)s, kPCFailureThresholdPctVar,
+ // PERFCHECK_THRESHOLD_PCT);
+
+ dispatch_apply(nthreads,
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), test);
+
+ dt_stat_end_batch(s, batch_size, t);
+ } while (!dt_stat_stable(s));
+ dt_stat_finalize(s);
+}
+
+// Test content. Each of the functions is called from six different test cases,
+// with singlethreaded true and false and MallocNanoZone set to 0 and V2.
+// The test content is biased towards the implementation of Nanov2.
+
+static void
+basic_perf_malloc_free_8_bytes(bool singlethreaded)
+{
+#define NUM_ALLOCS 512
+ run_test(^(size_t iteration __unused) {
+ void *ptrs[NUM_ALLOCS];
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ ptrs[i] = malloc(8);
+ }
+
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ free(ptrs[i]);
+ }
+ }, singlethreaded);
+#undef NUM_ALLOCS
+}
+
+static void
+basic_perf_malloc_free_8_bytes_multi_block(bool singlethreaded)
+{
+ // Use a large number of allocations to amortize the cost of scanning
+ // for a new block.
+#define NUM_ALLOCS 65535
+ run_test(^(size_t iteration __unused) {
+ void **ptrs = calloc(NUM_ALLOCS, sizeof(void *));
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ ptrs[i] = malloc(8);
+ }
+
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ free(ptrs[i]);
+ }
+ free(ptrs);
+ }, singlethreaded);
+#undef NUM_ALLOCS
+}
+
+static void
+basic_perf_malloc_free_different_size_classes(bool singlethreaded)
+{
+#define NUM_ALLOCS 512
+ run_test(^(size_t iteration __unused) {
+ void *ptrs[NUM_ALLOCS];
+ size_t sz = (iteration + 1) * 16;
+ if (sz > 256) {
+ // Too big for Nano.
+ return;
+ }
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ ptrs[i] = malloc(sz);
+ }
+
+ for (int i = 0; i < NUM_ALLOCS; i++) {
+ free(ptrs[i]);
+ }
+ }, singlethreaded);
+#undef NUM_ALLOCS
+}
+
+static void
+basic_perf_malloc_free_by_size_class(bool singlethreaded)
+{
+#define NUM_LOOPS 16
+ run_test(^(size_t iteration __unused) {
+ void *ptrs[NUM_LOOPS * 16];
+ int k = 0;
+ for (int i = 0; i < NUM_LOOPS; i++) {
+ for (int j = 0; j < 16; j++) {
+ ptrs[k++] = malloc(16 * j + 8);
+ }
+ }
+
+ for (int i = 0; i < k; i++) {
+ free(ptrs[i]);
+ }
+ }, singlethreaded);
+#undef NUM_LOOPS
+}
+
+static void
+basic_perf_malloc_free_by_size_class_offset(bool singlethreaded)
+{
+#define NUM_LOOPS 16
+ int cpu_number = _os_cpu_number();
+ run_test(^(size_t iteration __unused) {
+ void *ptrs[NUM_LOOPS * 16];
+ int k = 0;
+ for (int i = 0; i < NUM_LOOPS; i++) {
+ for (int j = 0; j < 16; j++) {
+ ptrs[k++] = malloc(16 * ((j + cpu_number) % 16) + 8);
+ }
+ }
+
+ for (int i = 0; i < k; i++) {
+ free(ptrs[i]);
+ }
+ }, singlethreaded);
+#undef NUM_LOOPS
+}
+
+// The tests that follow are grouped as follows:
+// - single-thread non-Nano version
+// - single-threaded NanoV2 version
+// - parallel non-Nano version
+// - parallel NanoV2 version
+// Each group probably could be built with a macro, but that would be harder
+// to debug when there is a problem.
+
+#pragma mark -
+#pragma mark 8-byte allocation/free
+
+T_DECL(basic_perf_serial_8_bytes, "Malloc/Free 8 bytes single-threaded",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_8_bytes(true);
+}
+
+T_DECL(basic_perf_serial_8_bytes_V2, "Malloc/Free 8 bytes single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_8_bytes, "Malloc/Free 8 bytes parallel",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_8_bytes(false);
+}
+
+T_DECL(basic_perf_parallel_8_bytes_V2, "Malloc/Free 8 bytes single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+#pragma mark -
+#pragma mark 8-byte allocation/free forcing block overflow with default scan policy
+
+T_DECL(basic_perf_serial_8_bytes_multi_block_default_scan_policy,
+ "Malloc/Free 8 bytes single-threaded with block overflow, default scan policy",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_8_bytes_multi_block(true);
+}
+
+T_DECL(basic_perf_serial_8_bytes_multi_block_default_scan_policy_V2,
+ "Malloc/Free 8 bytes single-threaded with block overflow, default scan policy on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes_multi_block(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_8_bytes_multi_block_default_scan_policy,
+ "Malloc/Free 8 bytes parallel with block overflow, default scan policy",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_8_bytes_multi_block(false);
+}
+
+T_DECL(basic_perf_parallel_8_bytes_multi_block_default_scan_policy_V2,
+ "Malloc/Free 8 bytes parallel with block overflow, default scan policy on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes_multi_block(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+#pragma mark -
+#pragma mark 8-byte allocation/free forcing block overflow with first-fit
+
+// This test only makes sense on Nanov2
+T_DECL(basic_perf_serial_8_bytes_multi_block_first_fit_V2,
+ "Malloc/Free 8 bytes single-threaded with block overflow, first-fit on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT), T_META_CHECK_LEAKS(false),
+ T_META_ENVVAR("MallocNanoZone=V2"),
+ T_META_ENVVAR("MallocNanoScanPolicy=firstfit"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes_multi_block(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_8_bytes_multi_block_first_fit_V2,
+ "Malloc/Free 8 bytes parallel with block overflow, first-fit on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"),
+ T_META_ENVVAR("MallocNanoScanPolicy=firstfit"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_8_bytes_multi_block(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+#pragma mark -
+#pragma mark Repeated allocation/free where each thread uses a different size class
+
+T_DECL(basic_perf_serial_different_size_classes,
+ "Malloc/Free in different size classes single-threaded",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_different_size_classes(false);
+}
+
+T_DECL(basic_perf_serial_different_size_classes_V2,
+ "Malloc/Free in different size classes single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_different_size_classes(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_different_size_classes,
+ "Malloc/Free in different size classes parallel",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_different_size_classes(false);
+}
+
+T_DECL(basic_perf_parallel_different_size_classes_V2,
+ "Malloc/Free in different size classes single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_different_size_classes(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+#pragma mark -
+#pragma mark Repeated allocation/free for each size class
+
+T_DECL(basic_perf_serial_by_size_class,
+ "Malloc/Free by size class single-threaded",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_by_size_class(true);
+}
+
+T_DECL(basic_perf_serial_by_size_class_V2,
+ "Malloc/Free by size class single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_by_size_class(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_by_size_class,
+ "Malloc/Free by size class parallel",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_by_size_class(false);
+}
+
+T_DECL(basic_perf_parallel_by_size_class_V2,
+ "Malloc/Free by size class single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_by_size_class(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+#pragma mark -
+#pragma mark Repeated allocation/free for each size class, offset by CPU
+
+T_DECL(basic_perf_serial_by_size_class_offset,
+ "Malloc/Free by size class with offset single-threaded",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_by_size_class_offset(true);
+}
+
+T_DECL(basic_perf_serial_by_size_class_offset_V2,
+ "Malloc/Free by size class with offset single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_by_size_class_offset(true);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
+T_DECL(basic_perf_parallel_by_size_class_offset,
+ "Malloc/Free by size class with offset parallel",
+ T_META_TAG_PERF, T_META_TAG_XZONE, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=0"))
+{
+ basic_perf_malloc_free_by_size_class_offset(false);
+}
+
+T_DECL(basic_perf_parallel_by_size_class_offset_V2,
+ "Malloc/Free by size class with offset single-threaded on V2",
+ T_META_TAG_PERF, T_META_ALL_VALID_ARCHS(NO),
+ T_META_LTEPHASE(LTE_POSTINIT),
+ T_META_ENVVAR("MallocNanoZone=V2"))
+{
+#if CONFIG_NANOZONE
+ basic_perf_malloc_free_by_size_class_offset(false);
+#else // CONFIG_NANOZONE
+ T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+