Skip to content

types/basetypes: Reduce set duplicate detection and equality to linear time - #1323

Open
samssh wants to merge 1 commit into
hashicorp:mainfrom
samssh:set-duplicate-and-equality-performance
Open

types/basetypes: Reduce set duplicate detection and equality to linear time#1323
samssh wants to merge 1 commit into
hashicorp:mainfrom
samssh:set-duplicate-and-equality-performance

Conversation

@samssh

@samssh samssh commented Sep 3, 2026

Copy link
Copy Markdown

Related Issue

Fixes #1322 (partially — this PR covers sites 1 and 2)

Description

SetType.Validate compared every pair of elements to find duplicates, and
SetValue.Equal scanned the whole other set for each element. Both are O(n²)
deep tftypes.Value comparisons, and each of those walks the value twice and
allocates an AttributePath per visited path. SetValue.contains made it
worse by iterating Elements(), which defensively copies the entire element
slice on every call — so comparing two n-element sets allocated n slices of
length n.

Both now group elements by their String() output and run the deep comparison
only within a group. Equal implies an identical String(), so nothing can be
missed.

That is not true for every element type: set equality ignores element order
while SetValue.String() renders elements in slice order, so two equal nested
sets can render differently. typeCanBeStringKeyed walks the element type tree
once and returns false if a set — or a dynamic type, whose concrete type is only
known at runtime — appears anywhere inside it; those keep the original pairwise
comparison. Object types are fine, because ObjectValue.String() sorts its
attribute names for exactly this reason.

Without that guard the change passes almost the whole suite and fails exactly
one test, TestValueSemanticEqualitySet/SetValue-SetValue-StringValuableWithSemanticEquals-true-diff-order.
With it, the full suite passes unchanged.

The existing BenchmarkSetTypeValidate* benchmarks did not measure any of this:
the helper ranged over an empty slice, so every one of them validated an empty
set. This populates it and adds matching BenchmarkSetValueEqual*, at
-benchtime 1x:

benchmark before after
SetTypeValidate1000 70.0 ms, 24 MB, 1,003,917 allocs 1.13 ms, 0.37 MB, 9,916 allocs
SetTypeValidate10000 7.82 s, 2.40 GB, 100,039,947 allocs 8.05 ms, 3.6 MB, 99,947 allocs
SetValueEqual1000 10.1 ms, 16.4 MB 0.67 ms, 0.20 MB
SetValueEqual10000 1.89 s, 1.64 GB 7.85 ms, 1.9 MB

SetType.Validate now compares each element against the earlier elements
sharing its key rather than the later ones, which reports the same duplicates
the same number of times.

Rollback Plan

  • If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

No.

@samssh
samssh requested a review from a team as a code owner September 3, 2026 07:06
@hashicorp-cla-app

hashicorp-cla-app Bot commented Sep 3, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@hashicorp-cla-app

Copy link
Copy Markdown

CLA assistant check

Thank you for your submission! We require that all contributors sign our Contributor License Agreement ("CLA") before we can accept the contribution. Read and sign the agreement

Learn more about why HashiCorp requires a CLA and what the CLA includes


AmirMohammad Sadat Shokouhi seems not to be a GitHub user.
You need a GitHub account to be able to sign the CLA.
If you have already a GitHub account, please add the email address used for this commit to your account.

Have you signed the CLA already but the status is still pending? Recheck it.

…tion and equality

SetType.Validate compared every pair of elements to detect duplicates, and
SetValue.Equal scanned the whole other set for each element, both O(n^2) deep
tftypes.Value comparisons. SetValue.contains additionally called Elements(),
which defensively copies the entire element slice on every call, so comparing
two n element sets allocated n slices of length n.

Group elements by their String() output, which is equal whenever Equal is, and
run the deep comparison only within a group. Element types with a set (or a
dynamic type) nested anywhere inside them keep the pairwise comparison, because
set equality ignores element order while String() does not.

The existing SetType.Validate benchmarks ranged over an empty slice and so
measured an empty set; populate it and add matching SetValue.Equal benchmarks.
@samssh
samssh force-pushed the set-duplicate-and-equality-performance branch from 1023809 to d5ac0a2 Compare September 3, 2026 07:13
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.

Three O(n²) paths in set handling make plan and refresh unusable for large sets

1 participant