[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