[libcxx-commits] [libcxx] [libc++] Add code size benchmarks (PR #226152)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Sep 24 05:59:27 PDT 2026


https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/226152

None

>From 5ed808a0a6985a51ef30991a1a9d16c7a12df582 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 21 Sep 2026 14:56:57 +0200
Subject: [PATCH] [libc++] Add code size benchmarks

---
 .../containers/sequence/vector.codesize.cpp   |  38 +++++
 libcxx/utils/bench-codesize                   | 132 ++++++++++++++++++
 libcxx/utils/libcxx/test/format.py            |   5 +
 3 files changed, 175 insertions(+)
 create mode 100644 libcxx/test/benchmarks/containers/sequence/vector.codesize.cpp
 create mode 100755 libcxx/utils/bench-codesize

diff --git a/libcxx/test/benchmarks/containers/sequence/vector.codesize.cpp b/libcxx/test/benchmarks/containers/sequence/vector.codesize.cpp
new file mode 100644
index 00000000000000..9f5961bddcb04f
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/sequence/vector.codesize.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <vector>
+#include <utility>
+
+#define CODESIZE_BM [[gnu::abi_tag("BENCHMARK_THIS")]]
+
+CODESIZE_BM auto bm_default_constructor() {
+  return std::vector<int>();
+}
+
+CODESIZE_BM auto bm_copy_constructor(const std::vector<int>& v) {
+  return v;
+}
+
+CODESIZE_BM auto bm_copy_assign(std::vector<int>& lhs, const std::vector<int>& rhs) {
+  lhs = rhs;
+}
+
+CODESIZE_BM auto bm_move_constructor(std::vector<int>&& v) {
+  return std::move(v);
+}
+
+CODESIZE_BM auto bm_push_back(std::vector<int>& v) {
+  v.push_back(1);
+}
+
+CODESIZE_BM auto bm_emplace_back(std::vector<int>& v) {
+  v.emplace_back(1);
+}
diff --git a/libcxx/utils/bench-codesize b/libcxx/utils/bench-codesize
new file mode 100755
index 00000000000000..7c7de75cc52912
--- /dev/null
+++ b/libcxx/utils/bench-codesize
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+import sys
+
+# Find the start of the actual symbol table
+for line in sys.stdin:
+  line = line.rstrip()
+  if line == "SYMBOL TABLE:":
+    break
+
+function_sizes = {}
+
+# for line in sys.stdin:
+#   print(line.rstrip())
+
+# Figure out interesting information (and drop the line, since it's just the file name)
+line = sys.stdin.readline().rstrip()
+address_size = len(line) - len(line.lstrip('0'))
+assert line.endswith(".codesize.cpp") # Make sure it's actually just the file name
+
+for line in sys.stdin:
+  line = line.rstrip()[address_size + 1:]
+
+  # If there is a newline we're done with the symbol table and get the relocations
+  if line == "":
+    break
+
+  # Extract the flag characters
+  flags = line[:7]
+  line = line[8:]
+
+  if flags[6] == " " and line.startswith("*UND*"):
+    function_sizes[line[line.rfind(" ") + 1:]] = -1
+
+  if flags[6] == "O":
+    function_sizes[line[line.rfind(" ") + 1:]] = -2
+
+  # Drop everything else that's not a function
+  if flags[6] != "F":
+    continue
+
+  # Drop the section name
+  line = line[line.find("\t") + 1:]
+
+  # Get the size of the function
+  code_size = int(line[:line.find(" ")], 16)
+  line = line[line.find(" "):]
+
+  # Drop an additional annotations
+  line = line[line.rfind(" ") + 1:]
+
+  # The remainder is the mangled function name
+  function_name = line
+
+  function_sizes[function_name] = code_size
+
+edges = {}
+
+while True:
+  EOF = True
+  found_line = False
+  for line in sys.stdin:
+    EOF = False
+    line = line.rstrip()
+    if line.startswith("RELOCATION RECORDS FOR [.text."):
+      found_line = True
+      break
+  if EOF or not found_line:
+    break
+  current_func = line[line.rfind(".text.") + 6:-2]
+  edges[current_func] = list()
+
+  sys.stdin.readline() # remove table header
+
+  for line in sys.stdin:
+    line = line.rstrip()
+    if line == "":
+      break
+    edges[current_func].append(line[line.rfind(" ") + 1:line.rfind("-")])
+
+accumulated_sizes = {}
+
+print_only = False
+
+def get_accumulated_size(func):
+  if func in accumulated_sizes:
+    if not print_only:
+      accumulated_sizes[func] = (accumulated_sizes[func][0], False)
+    return accumulated_sizes[func]
+
+  ConsiderUnique = "BENCHMARK_THIS" not in func
+
+  # For recurice cases just don't count the same function twice. This is inaccurate for actual recursion, but that's
+  # rarely used in libc++, so it doesn't matter too much.
+  accumulated_sizes[func] = (0, ConsiderUnique)
+
+  acc = function_sizes[func]
+
+  if acc < 0: # We have an external symbol or something that's not a function
+    return (0, False)
+
+  if func not in edges:
+    accumulated_sizes[func] = (acc, ConsiderUnique)
+    return (acc, ConsiderUnique)
+
+  for child in edges[func]:
+    acc += get_accumulated_size(child)[0]
+  accumulated_sizes[func] = (acc, ConsiderUnique)
+  return (acc, ConsiderUnique)
+
+for func in function_sizes:
+  if "B14BENCHMARK_THIS" in func:
+    get_accumulated_size(func)
+
+print_only = True
+
+def print_children(level, e):
+  for edge in e:
+    size, unique = get_accumulated_size(edge)
+    if function_sizes[edge] >= 0:
+      print("  " * level + f"{edge} {size}:{function_sizes[edge]}")
+      if unique and edge in edges:
+        print_children(level + 1, edges[edge])
+
+for func in function_sizes:
+  size, unique = get_accumulated_size(func)
+  if unique or function_sizes[func] < 0:
+    continue
+  short_name = func.replace("B14BENCHMARK_THIS", "")
+  print(f"{"[multiuse] " if short_name == func else "[benchmark] "}{short_name}: {size}:{function_sizes[func]}")
+  if func in edges:
+    print_children(1, edges[func])
+  print()
diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py
index 2f449e234e6d5e..106994d2ce9397 100644
--- a/libcxx/utils/libcxx/test/format.py
+++ b/libcxx/utils/libcxx/test/format.py
@@ -274,6 +274,7 @@ class CxxStandardLibraryTest(lit.formats.FileBasedTest):
     def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig):
         SUPPORTED_SUFFIXES = [
             "[.]bench[.]cpp$",
+            "[.]codesize[.]cpp$",
             "[.]pass[.]cpp$",
             "[.]pass[.]mm$",
             "[.]compile[.]pass[.]cpp$",
@@ -367,6 +368,10 @@ def execute(self, test, litConfig):
                 parse_results = os.path.join(LIBCXX_UTILS, 'parse-google-benchmark-results')
                 steps += [f"{parse_results} %{{temp}}/benchmark-result.json --output-format=lnt > %{{temp}}/results.lnt"]
             return self._executeShTest(test, litConfig, steps)
+        elif filename.endswith(".codesize.cpp"):
+            steps = ["%dbg(COMPILED WITH) %{cxx} %s %{flags} %{compile_flags} -c -ffunction-sections -o %t.o",
+                     f"%dbg(EXECUTED AS) llvm-objdump -tr %t.o | {os.path.join(LIBCXX_UTILS, "bench-codesize")} | llvm-cxxfilt"]
+            return self._executeShTest(test, litConfig, steps)
         elif re.search('[.]gen[.][^.]+$', filename): # This only happens when a generator test is not supported
             return self._executeShTest(test, litConfig, [])
         else:



More information about the libcxx-commits mailing list