[PATCH v4] benchtest: malloc tcache hotpath benchtest.

Cupertino Miranda cupertino.miranda@oracle.com
Fri May 9 11:02:22 GMT 2025


Hi everyone,

New version with multiple small fixes.
I noticed that there was an unexpected performance loss when alloc_size
was 1024.
While exploring I notice that for alloc_size 1016 I get:
 "time_per_iteration": 14.3453
and for 1017:
 "time_per_iteration": 16.3697.
Could this be related to CPU cache effects? No idea!

Values seem quiet stable both below/above alloc_size 1016/1017,
respectively.

Looking forward to your review.

Cheers,
Cupertino


Changes from v1:
 - Changed block size randomizer to get proper distribution of
   allocations.
 - Drop pre-filling of tcaches.
Changes from v2:
 - Simplified test to focus on performance of tcache hotpaths, ignoring
   any RSS measurements.
Changes from v3:
 - Added call to bench_start to ramp up cpu.
 - Do a preload allocation and free to avoid measuring non tcache paths.

Existing benchtests for malloc infrastructure seem to be rather generic
to test global malloc implementation performance.  This new benchtest
focus on reducing any non tcache related side effects, allowing to more
realistically predict performance impacts of tcache code changes.
The test was inpired in bench-[cm]alloc-thread code, with severe
simplifications:
 - forces single thread execution, reducing concurrency side-effects,
   like cache incoherence penalties due simultaneous writes to the same
   cache pages;
 - Focus on allocating and deallocating a single size for all the
   duration of the benchmark. Since all it does is allocate and
   deallocate, it will measure the tcache hotpath without any
   side-effects.
 - Allows to specify the allocation size as input argument.
---
 benchtests/Makefile              |   9 ++
 benchtests/bench-calloc-tcache.c |  22 +++++
 benchtests/bench-malloc-tcache.c | 157 +++++++++++++++++++++++++++++++
 3 files changed, 188 insertions(+)
 create mode 100644 benchtests/bench-calloc-tcache.c
 create mode 100644 benchtests/bench-malloc-tcache.c

diff --git a/benchtests/Makefile b/benchtests/Makefile
index 74142da326..d4d2be0fde 100644
--- a/benchtests/Makefile
+++ b/benchtests/Makefile
@@ -329,8 +329,10 @@ ifeq (${BENCHSET},)
 bench-malloc := \
   calloc-simple \
   calloc-thread \
+  calloc-tcache \
   malloc-simple \
   malloc-thread \
+  malloc-tcache \
   # bench-malloc
 else
 bench-malloc := $(filter malloc-%,${BENCHSET})
@@ -453,9 +455,11 @@ VALIDBENCHSETNAMES := \
   bench-string \
   calloc-simple \
   calloc-thread \
+  calloc-tcache \
   hash-benchset \
   malloc-simple \
   malloc-thread \
+  malloc-tcache \
   math-benchset \
   stdio-benchset \
   stdio-common-benchset \
@@ -498,6 +502,11 @@ bench-malloc: $(binaries-bench-malloc)
 			echo "Running $${run} $${thr}"; \
 			$(run-bench) $${thr} > $${run}-$${thr}.out; \
 		done;\
+	  elif basename $${run} | grep -q "bench-[cm]alloc-tcache"; then \
+		for thr in 64 512 1024; do \
+			echo "Running $${run} $${thr}"; \
+			$(run-bench) $${thr} > $${run}-$${thr}.out; \
+		done;\
 	  else \
 		for thr in 8 16 32 64 128 256 512 1024 2048 4096; do \
 		  echo "Running $${run} $${thr}"; \
diff --git a/benchtests/bench-calloc-tcache.c b/benchtests/bench-calloc-tcache.c
new file mode 100644
index 0000000000..5303f872b8
--- /dev/null
+++ b/benchtests/bench-calloc-tcache.c
@@ -0,0 +1,22 @@
+/* Benchmark calloc and free functions.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#define TEST_FUNC(size) calloc (1, size)
+#define TEST_NAME "calloc"
+
+#include "bench-malloc-tcache.c"
diff --git a/benchtests/bench-malloc-tcache.c b/benchtests/bench-malloc-tcache.c
new file mode 100644
index 0000000000..43633f3ee2
--- /dev/null
+++ b/benchtests/bench-malloc-tcache.c
@@ -0,0 +1,157 @@
+/* Benchmark tcache hotpath allocations.
+   Copyright (C) 2013-2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef TEST_FUNC
+# define TEST_FUNC(size) malloc(size)
+# define TEST_NAME "malloc"
+#endif
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "bench-util.h"
+#include "bench-util.c"
+#include "bench-timing.h"
+#include "json-lib.h"
+
+/* Benchmark duration in seconds.  */
+#define BENCHMARK_DURATION	3
+
+static volatile bool timeout;
+size_t alloc_size;
+
+static void
+alarm_handler (int signum)
+{
+  timeout = true;
+}
+
+/* Allocate and free blocks in a random order.  */
+static size_t
+malloc_benchmark_loop (void *elem)
+{
+  size_t iters = 0;
+  while (!timeout)
+    {
+      elem = TEST_FUNC (alloc_size);
+      free (elem);
+      iters++;
+    }
+
+  return iters;
+}
+
+static timing_t
+do_benchmark (size_t *iters)
+{
+  timing_t elapsed = 0;
+  timing_t start, stop;
+  void *elem = NULL;
+
+  /* Ramp up cpu before measuring.  */
+  bench_start ();
+
+  /* Preload tcache not to measure non tcache allocation time.  */
+  elem = TEST_FUNC (alloc_size);
+  free (elem);
+
+  TIMING_NOW (start);
+  *iters = malloc_benchmark_loop (elem);
+  TIMING_NOW (stop);
+
+  TIMING_DIFF (elapsed, start, stop);
+
+  return elapsed;
+}
+
+static void usage (const char *name)
+{
+  fprintf (stderr, "%s: <alloc_size>\n", name);
+  exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+  timing_t cur;
+  size_t iters = 0;
+  json_ctx_t json_ctx;
+  double d_total_s, d_total_i;
+  struct sigaction act;
+
+  if (argc == 1)
+    alloc_size = 1024;
+  else if (argc == 2)
+    {
+      long ret;
+
+      errno = 0;
+      ret = strtol (argv[1], NULL, 10);
+
+      if (errno || ret == 0)
+	usage (argv[0]);
+
+      alloc_size = ret;
+    }
+  else
+    usage (argv[0]);
+
+  json_init (&json_ctx, 0, stdout);
+
+  json_document_begin (&json_ctx);
+
+  json_attr_string (&json_ctx, "timing_type", TIMING_TYPE);
+
+  json_attr_object_begin (&json_ctx, "functions");
+
+  json_attr_object_begin (&json_ctx, TEST_NAME);
+
+  json_attr_object_begin (&json_ctx, "");
+
+  memset (&act, 0, sizeof (act));
+  act.sa_handler = &alarm_handler;
+
+  sigaction (SIGALRM, &act, NULL);
+
+  alarm (BENCHMARK_DURATION);
+
+  cur = do_benchmark (&iters);
+
+  d_total_s = cur;
+  d_total_i = iters;
+
+  json_attr_double (&json_ctx, "alloc_size", alloc_size);
+  json_attr_double (&json_ctx, "duration", d_total_s);
+  json_attr_double (&json_ctx, "iterations", d_total_i);
+  json_attr_double (&json_ctx, "time_per_iteration", d_total_s / d_total_i);
+
+  json_attr_object_end (&json_ctx);
+
+  json_attr_object_end (&json_ctx);
+
+  json_attr_object_end (&json_ctx);
+
+  json_document_end (&json_ctx);
+
+  return 0;
+}
-- 
2.39.5



More information about the Libc-alpha mailing list