[PATCH v6 10/14] perf pmu-events: Split big_c_string storage into standalone compilation unit
From: Ian Rogers
Date: Mon May 18 2026 - 00:53:18 EST
Currently, jevents.py emits both the massive 2.8 MB big_c_string literal
and tens of thousands of compact_pmu_event struct arrays into a single
pmu-events.c compilation unit. Compiling this giant file takes ~2.2 seconds
on a single CPU core during Kbuild startup.
Refactor jevents.py to emit big_c_string into a dedicated
pmu-events-string.c compilation unit. This allows Kbuild to compile
pmu-events.o and pmu-events-string.o simultaneously in parallel across
two separate CPU cores, preserving 100% string deduplication and zero
dynamic ELF relocations while cutting C compilation latency in half.
Add pmu-events-string.c to tools/perf/.gitignore to ensure in-tree Kbuild
runs do not leave untracked generated files in the working directory.
To guarantee 100% backward compatibility with GNU Make 4.0+ (avoiding the
Make 4.3+ grouped target &: syntax which causes older Make versions like
4.2.1 to spawn multiple concurrent jevents.py processes during parallel
builds), implement a robust dependency chaining pattern:
$(PMU_EVENTS_C): $(JEVENTS_DEPS)
$(PMU_EVENTS_STRING_C): $(PMU_EVENTS_C)
@:
This ensures jevents.py is invoked exactly once. If jevents.py aborts
early, Make's .DELETE_ON_ERROR: purges pmu-events.c, guaranteeing that
subsequent Make invocations correctly re-execute the script and overwrite
pmu-events-string.c. In jevents.py, explicitly close output_file first
and output_string_file second at the tail of main() to guarantee that
pmu-events-string.c receives a filesystem timestamp greater than or equal
to pmu-events.c, completely avoiding redundant incremental rebuilds.
Tested-by: James Clark <james.clark@xxxxxxxxxx>
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
---
tools/perf/.gitignore | 1 +
tools/perf/Makefile.perf | 4 ++--
tools/perf/pmu-events/Build | 16 +++++++++++++++-
tools/perf/pmu-events/jevents.py | 23 +++++++++++++++++++----
4 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore
index 0f9451a6e39c..3b968c5158b8 100644
--- a/tools/perf/.gitignore
+++ b/tools/perf/.gitignore
@@ -38,6 +38,7 @@ arch/*/include/generated/
trace/beauty/generated/
pmu-events/arch/common/common/legacy-cache.json
pmu-events/pmu-events.c
+pmu-events/pmu-events-string.c
pmu-events/jevents
pmu-events/metric_test.log
pmu-events/empty-pmu-events.log
diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
index 96a68723109f..b8a81c9749a8 100644
--- a/tools/perf/Makefile.perf
+++ b/tools/perf/Makefile.perf
@@ -925,7 +925,7 @@ bpf-skel-clean:
pmu-events-clean:
ifeq ($(OUTPUT),)
$(call QUIET_CLEAN, pmu-events) $(RM) \
- pmu-events/pmu-events.c \
+ pmu-events/pmu-events*.c \
pmu-events/metric_test.log \
pmu-events/test-empty-pmu-events.c \
pmu-events/empty-pmu-events.log
@@ -933,7 +933,7 @@ ifeq ($(OUTPUT),)
-name 'extra-metricgroups.json' -delete
else # When an OUTPUT directory is present, clean up the copied pmu-events/arch directory.
$(call QUIET_CLEAN, pmu-events) $(RM) -r $(OUTPUT)pmu-events/arch \
- $(OUTPUT)pmu-events/pmu-events.c \
+ $(OUTPUT)pmu-events/pmu-events*.c \
$(OUTPUT)pmu-events/metric_test.log \
$(OUTPUT)pmu-events/test-empty-pmu-events.c \
$(OUTPUT)pmu-events/empty-pmu-events.log
diff --git a/tools/perf/pmu-events/Build b/tools/perf/pmu-events/Build
index dc1df2d57ddc..95172a2a851f 100644
--- a/tools/perf/pmu-events/Build
+++ b/tools/perf/pmu-events/Build
@@ -1,7 +1,12 @@
EMPTY_PMU_EVENTS_C = pmu-events/empty-pmu-events.c
# pmu-events.c will be generated by jevents.py or copied from EMPTY_PMU_EVENTS_C
PMU_EVENTS_C = $(OUTPUT)pmu-events/pmu-events.c
+PMU_EVENTS_STRING_C = $(OUTPUT)pmu-events/pmu-events-string.c
+
pmu-events-y += pmu-events.o
+ifneq ($(NO_JEVENTS),1)
+pmu-events-y += pmu-events-string.o
+endif
# pmu-events.c file is generated in the OUTPUT directory so it needs a
# separate rule to depend on it properly
@@ -9,6 +14,10 @@ $(OUTPUT)pmu-events/pmu-events.o: $(PMU_EVENTS_C)
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
+$(OUTPUT)pmu-events/pmu-events-string.o: $(PMU_EVENTS_STRING_C)
+ $(call rule_mkdir)
+ $(call if_changed_dep,cc_o_c)
+
# Message for $(call echo-cmd,cp), possibly remove the src file from
# the destination to save space in the build log.
quiet_cmd_cp = COPY $(patsubst %$<,%,$@) <- $<
@@ -118,6 +127,7 @@ CUR_OUT_JSON := $(shell [ -d $(OUT_DIR) ] && find $(OUT_DIR) -type f)
# Things in the OUTPUT directory but shouldn't be there as computed by
# OUT_JSON and GEN_JSON.
+
ORPHAN_FILES := $(filter-out $(OUT_JSON) $(GEN_JSON),$(CUR_OUT_JSON))
# Message for $(call echo-cmd,mkd). There is already a mkdir message
@@ -224,6 +234,10 @@ endif
# and inputs are dependencies.
$(PMU_EVENTS_C): $(JEVENTS_DEPS)
$(call rule_mkdir)
- $(Q)$(call echo-cmd,gen)$(PYTHON) $(JEVENTS_PY) $(JEVENTS_ARCH) $(JEVENTS_MODEL) $(OUT_DIR) $@
+ $(Q)$(call echo-cmd,gen)$(PYTHON) $(JEVENTS_PY) $(JEVENTS_ARCH) $(JEVENTS_MODEL) \
+ $(OUT_DIR) $(PMU_EVENTS_C) $(PMU_EVENTS_STRING_C)
+
+$(PMU_EVENTS_STRING_C): $(PMU_EVENTS_C)
+ @:
endif # ifeq ($(NO_JEVENTS),1)
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 3a1bcdcdc685..7344940e776a 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -1422,6 +1422,8 @@ such as "arm/cortex-a34".''',
)
ap.add_argument(
'output_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=sys.stdout)
+ ap.add_argument(
+ 'output_string_file', type=argparse.FileType('w', encoding='utf-8'), nargs='?', default=None)
_args = ap.parse_args()
_args.output_file.write(f"""
@@ -1463,10 +1465,20 @@ struct pmu_table_entry {
ftw(arch_path, [], preprocess_one_file)
_bcs.compute()
- _args.output_file.write('static const char *const big_c_string =\n')
- for s in _bcs.big_string:
- _args.output_file.write(s)
- _args.output_file.write(';\n\n')
+ if not _args.output_string_file:
+ _args.output_file.write('static const char *const big_c_string =\n')
+ for s in _bcs.big_string:
+ _args.output_file.write(s)
+ _args.output_file.write(';\n\n')
+ else:
+ _args.output_string_file.write('/* SPDX-License-Identifier: GPL-2.0 */\n')
+ _args.output_string_file.write('/* Autogenerated by jevents.py */\n')
+ _args.output_string_file.write('extern const char big_c_string[];\n')
+ _args.output_string_file.write('const char big_c_string[] =\n')
+ for s in _bcs.big_string:
+ _args.output_string_file.write(s)
+ _args.output_string_file.write(';\n')
+ _args.output_file.write('extern const char big_c_string[];\n\n')
for arch in archs:
arch_path = f'{_args.starting_dir}/{arch}'
ftw(arch_path, [], process_one_file)
@@ -1476,6 +1488,9 @@ struct pmu_table_entry {
print_mapping_table(archs)
print_system_mapping_table()
print_metricgroups()
+ _args.output_file.close()
+ if _args.output_string_file:
+ _args.output_string_file.close()
if __name__ == '__main__':
main()
--
2.54.0.563.g4f69b47b94-goog