Skip to content

Improve performance of loading dump files in cppcheckdata.py - #8989

Open
pfultz2 wants to merge 4 commits into
cppcheck-opensource:mainfrom
pfultz2:perf-addon-load
Open

pfultz2 wants to merge 4 commits into
cppcheck-opensource:mainfrom
pfultz2:perf-addon-load

Conversation

@pfultz2

@pfultz2 pfultz2 commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Loading a dump file in cppcheckdata.py is now about 2.5x faster. Addons that iterate over the configurations more than once no longer parse the dump file on each pass. The parsed objects are unchanged.

  • Objects are built directly from the XML parser callbacks, without creating ElementTree elements first.
  • The cyclic garbage collector is disabled while parsing, because it took about half of the loading time. It is enabled again before each configuration is returned to the caller.
  • The CppcheckData constructor skips the <dump> elements and parses only the rest of the file. Before, it parsed the whole file to reach the raw tokens and suppressions, which are written after the configurations.
  • data.configurations loads the configurations once and keeps them. Later accesses and iterconfigurations() reuse them, while iterconfigurations() on its own still parses one configuration at a time. misc.py now uses data.configurations for its three passes, and misra.py --verify parses the file once instead of twice.

Every parsed object was compared between the old and new code on dumps of the current layout, the old layout (raw tokens before the configurations) and clang-import dumps. New tests are in test/addon.

Timings

Fastest of several runs on Python 3.12, with peak memory after the slash.

Load the dump file and iterate over the configurations once:

Dump Size Configurations Before After Speedup
misra-test.c 26 MB 6 2.06s / 239 MB 0.91s / 181 MB 2.3x
token.cpp 15 MB 1 1.20s / 161 MB 0.53s / 157 MB 2.3x
valueflow.cpp 18 MB 1 1.93s / 275 MB 0.78s / 243 MB 2.5x
symboldatabase.cpp 33 MB 1 3.44s / 363 MB 1.31s / 357 MB 2.6x

misc.py (three passes over the configurations):

Dump Before After Speedup
misra-test.c 5.08s / 239 MB 1.28s / 253 MB 4.0x
token.cpp 3.30s / 169 MB 0.73s / 158 MB 4.5x
valueflow.cpp 4.97s / 326 MB 1.13s / 243 MB 4.4x
symboldatabase.cpp 9.03s / 422 MB 1.92s / 358 MB 4.7x

misc.py uses more memory on misra-test.c because it now keeps all 6 configurations in memory at once.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

I found no problems in this PR. I compared the objects the base and PR versions of cppcheckdata.py build and found no differences. That covers the platform, language, files, raw tokens, suppressions and every configuration, on multi-configuration C, C++, real-world (lib/token.cpp, 10 configurations) and old-layout dumps. On a truncated dump and on an empty dump, the PR raises the same ParseError as base and leaves the garbage collector enabled. Addon output is the same, and misc.py on lib/token.cpp.dump dropped from 27.0s to 6.5s.

One behaviour change is worth knowing about, though I don't count it as a bug. data.configurations now returns the same cached list every time, and iterconfigurations() reuses it after that. So if an addon changes the configuration objects or the list in one pass, later passes now see those changes, where before each pass parsed the file again. None of the bundled addons depends on that.

Testing

Build: I ran make -j8 cppcheck in pr/, and it built.

Existing tests (all pass):

  • python3 -m pytest test/addon --cppcheck-binary=$PWD/cppcheck: 114 passed, 3 xfailed. Without --cppcheck-binary every fixture errors because no cppcheck is on PATH.
  • PYTHONPATH=addons python3 -m pytest addons/test: 25 passed. This includes the misra --verify and y2038 tests. Without PYTHONPATH=addons the tests can't import cppcheckdata.
  • test/cli/dumpfile_test.py and test/cli/clang-import_test.py: 54 passed.
  • test/cli/other_test.py -k "addon or misra or dump or premium": 44 passed, 1 skipped.

Old vs new comparison: I wrote scratch/cmp.py, which loads base/addons/cppcheckdata.py and pr/addons/cppcheckdata.py side by side. For each dump it turns the header data and every configuration into nested dicts of their attributes, two levels of nested objects deep (deeper ones are compared by Id), and compares them. I ran it on these dumps:

  • t1.c: 3 #ifdef configurations, an inline suppression, typedefs, varargs and string macros.
  • t2.cpp: a template, a lambda, virtual functions, and "<dump x>" and </dump> inside a string literal and a comment.
  • lib/token.cpp: 10 configurations.
  • An old-layout dump I built from t1.c.dump by moving <rawtokens> and <suppressions> before the first <dump>.

All of them had identical header data and configurations, and gc.isenabled() was True afterwards.

Error cases: For a dump truncated after 3000 bytes and for an empty file, base and PR raise the same ParseError, and the garbage collector is still enabled afterwards.

Addon output:

  • misc.py --cli on addons/test/misc-test.cpp: same output on base and PR.
  • misc.py on lib/token.cpp.dump: same output; time 27.0s on base vs 6.5s on the PR.
  • misra.py on lib/token.cpp.dump: same output.

Automated review of 7a3fe1b by Claude. The pull request was built and tested locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants