Identify Bottlenecks in User-Defined Coding Standards
R2026bWhen a user-defined coding standard takes too long to analyze your codebase, the cause is
usually a small number of predicates that produce very large intermediate tables or perform
expensive operations. The polyspace-query-language profile command runs a
Polyspace®
Bug Finder™ analysis with profiling enabled and reports per-predicate metrics — runtime,
table size, and memory usage — so you can pinpoint the bottleneck, rewrite the predicate, and
verify the improvement.
To use profiling effectively, you need a basic understanding of how PQL executes your
rules under the hood. PQL is a declarative language: you describe what to
find, not how to find it. Under the hood, PQL compiles to Datalog and
runs on the Soufflé engine. Each predicate in a when clause produces an
intermediate table of all matching results. When you write
Cpp.Function.is(&func), the engine generates a table of every
function in the codebase. When you add and func.name(&name), it joins
that table with function names. The logical and between predicates is a
join — the engine combines rows from both tables. If the tables are large, the join is
expensive. The logical or is a union — results from both branches are
merged. Because the engine materializes all matching rows with no early exit, a predicate that
matches broadly (such as Cpp.Identifier.is, which matches every identifier
in the source code) generates a very large table that slows all subsequent operations.
Write a Rule and Profile It
Consider a coding rule: function names must not start with an
underscore. A straightforward implementation uses the syntactic class
Cpp.Identifier to find all identifiers in the source code, then walks
up the syntax tree to check whether each identifier belongs to a function definition:
The rule works correctly — it finds all functions whose names start with an underscore. To check how it performs, package the project and run the profiler:
polyspace-query-language package polyspace-query-language profile -sources test_profiling.c
You can append any Polyspace
Bug Finder option to the profile command.
Note
The profile command runs a full Polyspace
Bug Finder analysis with profiling enabled. Use source files that are representative of
your production codebase. On small files, differences between implementations might not be
visible.
Interpret the Profiling Results
The profiler prints three ranked tables to the console. Each table lists the top predicates by one metric:
Runtime (ms) — Time the engine spent computing the predicate's result table.
Number of Entries (tuples) — How many rows the predicate's result table contains. A large number means the predicate matches broadly.
RSS (kB) — Memory consumed to store the intermediate table.
The profiler also generates an HTML report at
.polyspace/.profile/profile_report.html with sortable columns and
per-predicate source mapping.
For the rule above, on a C file with approximately 20 functions and 200 lines of code, the profiling results show:
Runtime — The defect
naming.FuncNameLeadingUnderscoreruns in 0.95 ms.Number of Entries — The
Cpp.Identifiertable contains 290 tuples. This means the engine generated a table with every identifier in the file — variable names, type names, parameters, labels, and function names — even though only 20 of those identifiers are function names.
The Cpp.Identifier table is the bottleneck. It is the largest table
in the profiling results, and the defect must walk up the syntax tree from each of the 290
identifiers to check whether it belongs to a function definition. On a large codebase where
the identifier table can contain tens of thousands of entries, this cost grows
proportionally.
Rewrite the Rule and Re-Profile
The profiling results show that the rule is slow because
Cpp.Identifier matches far more entities than necessary. Instead of
iterating over all identifiers and filtering down to functions, use the semantic class
Cpp.Function, which directly targets function declarations. Replace
naming.pql with:
package naming
defect FuncNameLeadingUnderscore =
when
Cpp.Function.is(&func)
and func.name(&name)
and name.regexSearch("^_")
raise "Function name '{name}' starts with an underscore"
on funcThis rewritten defect finds the same violations. The difference is in the entry point:
Cpp.Function.is generates a table of only function declarations, not
every identifier. Re-package and re-profile:
polyspace-query-language package polyspace-query-language profile -sources test_profiling.c
The profiling results for the rewritten rule show:
Runtime — The defect
naming.FuncNameLeadingUnderscorenow runs in 0.14 ms, nearly 7× faster than before.Number of Entries — The
Cpp.Functiontable contains 20 tuples instead of the 290 tuples fromCpp.Identifier. The engine processes only the entities it needs.
The improvement comes entirely from choosing a more specific entry point. On a large codebase, where the ratio of identifiers to functions is even larger, the speedup is more pronounced.
Optimization Strategies
The rewrite above illustrates the most impactful optimization: replacing a broad syntactic class with a targeted semantic class. Other strategies include:
Use a semantic class instead of a syntactic class. Semantic classes such as
Cpp.Function,Cpp.Variable, andCpp.Macrooperate on resolved entities whose tables are much smaller than the corresponding syntactic classes. See Choose Between Semantic and Syntactic Classes.Filter early. Place restrictive predicates before expensive ones. For example, filter by type (
type.isIntegral()) before performing string operations on the name.Keep predicates short. Each predicate compiles to nested joins. Shorter predicates mean fewer joins and smaller intermediate tables.
Mark shared predicates with
#[NoInline]. By default, PQL inlines predicates at each call site. A predicate used in multiple rules ororbranches is recomputed independently in each location. The#[NoInline]annotation causes the engine to compute the predicate once and reuse the result.
After each change, re-run polyspace-query-language profile and
compare the metrics to confirm the improvement.