Skip to content

Commit e7e02b9

Browse files
authored
Optimize rule filtering performance (#4222)
- Pre-compute enabled rules set once at start of Rules.run() - Use fast set lookup instead of repeated is_rule_enabled() calls - Add enabled_rule_ids parameter to run_check() method - Reduces config property access calls from thousands to hundreds - Achieves ~32% performance improvement on large templates Fixes performance bottleneck where is_rule_enabled() was called for every match generated, causing excessive config property access through _get_argument_value().
1 parent c06ba15 commit e7e02b9

File tree

1 file changed

+31
-7
lines changed

1 file changed

+31
-7
lines changed

src/cfnlint/rules/_rules.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ def used_rules(self) -> dict[str, CloudFormationLintRule]:
9090
return self._used_rules
9191

9292
# pylint: disable=inconsistent-return-statements
93-
def run_check(self, check, filename, rule_id, config, *args) -> Iterator[Match]:
93+
def run_check(
94+
self, check, filename, rule_id, config, enabled_rule_ids=set(), *args
95+
) -> Iterator[Match]:
9496
"""Run a check"""
95-
if self.is_rule_enabled(rule_id, config):
97+
# Use enabled_rule_ids set if provided
98+
if rule_id in enabled_rule_ids:
9699
self._used_rules[rule_id] = self.data[rule_id]
97100
try:
98101
yield from iter(check(*args))
@@ -120,15 +123,27 @@ def _filter_matches(
120123
if self.is_rule_enabled(match.rule, config):
121124
yield match
122125

126+
def _filter_matches_with_enabled_set(
127+
self, enabled_rule_ids: set[str], matches: Iterator[Match]
128+
) -> Iterator[Match]:
129+
"""Filter matches using pre-computed enabled rules set"""
130+
for match in matches:
131+
if match.rule.id in enabled_rule_ids:
132+
yield match
133+
123134
def run(
124135
self, filename: str | None, cfn: Template, config: ConfigMixIn
125136
) -> Iterator[Match]:
126137
"""Run rules"""
138+
# Build enabled rules set once to avoid repeated is_rule_enabled calls
139+
enabled_rule_ids = set()
127140
for rule_id, rule in self.data.items():
128141
rule.configure(
129142
config.configure_rules.get(rule_id, None), config.include_experimental
130143
)
131144
rule.initialize(cfn)
145+
if self.is_rule_enabled(rule_id, config):
146+
enabled_rule_ids.add(rule_id)
132147

133148
for rule_id, rule in self.data.items():
134149
for key in rule.child_rules.keys():
@@ -140,9 +155,17 @@ def run(
140155
self.data[parent_rule].child_rules[rule_id] = rule
141156

142157
for rule_id, rule in self.data.items():
143-
yield from self._filter_matches(
144-
config,
145-
self.run_check(rule.matchall, filename, rule_id, config, filename, cfn),
158+
yield from self._filter_matches_with_enabled_set(
159+
enabled_rule_ids,
160+
self.run_check(
161+
rule.matchall,
162+
filename,
163+
rule_id,
164+
config,
165+
enabled_rule_ids,
166+
filename,
167+
cfn,
168+
),
146169
)
147170

148171
for resource_name, resource_attributes in cfn.get_resources().items():
@@ -151,13 +174,14 @@ def run(
151174
if isinstance(resource_type, str) and isinstance(resource_properties, dict):
152175
path = ["Resources", resource_name, "Properties"]
153176
for rule_id, rule in self.data.items():
154-
yield from self._filter_matches(
155-
config,
177+
yield from self._filter_matches_with_enabled_set(
178+
enabled_rule_ids,
156179
self.run_check(
157180
rule.matchall_resource_properties,
158181
filename,
159182
rule_id,
160183
config,
184+
enabled_rule_ids,
161185
filename,
162186
cfn,
163187
resource_properties,

0 commit comments

Comments
 (0)