Eliminate redundant associative container lookups across the compiler#16606
Open
k06a wants to merge 2 commits intoargotorg:developfrom
Open
Eliminate redundant associative container lookups across the compiler#16606k06a wants to merge 2 commits intoargotorg:developfrom
k06a wants to merge 2 commits intoargotorg:developfrom
Conversation
…pass access (+added code style rule)
|
Thank you for your contribution to the Solidity compiler! A team member will follow up shortly. If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother. If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix. |
…ser pipeline from std::map/set to std::unordered_map/set
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bottom-up optimization of compiler internals. No compilation logic, code generation, or optimizer behavior changes. Bytecode is bit-for-bit identical to the baseline (verified with
--metadata-hash none).Commit 1 — Eliminate redundant associative container lookups
count/find+at/operator[]) with singlefind+ iterator reuse,try_emplace, orinsert().secondacross 42 files inlibevmasm,libyul,libsolidity,libsolutil.std::set/std::maptostd::unordered_set/std::unordered_mapin 8 files where iteration order is never used (MultiUseYulFunctionCollector,FullInliner,InlinableExpressionFunctionFinder,EVMCodeTransform,AsmAnalysis,PathGasMeter,AST.cpp,TypeSystem.h).std::erase_if(C++20) inKnownState.cpp.CODING_STYLE.mdcodifying these best practices.Commit 2 — Convert hot YulName/FunctionHandle-keyed containers in the Yul optimiser pipeline
Extends the same refactor into the Yul optimiser's hot path, specifically the data-flow / semantics / control-flow analysis layers:
std::hash<BuiltinHandle>(inBuiltins.h) andstd::hash<FunctionHandle>(std::variant<YulName, BuiltinHandle>). RouteBuiltins.hthroughASTForward.hso the specialisation is visible whereverFunctionHandleis used.State::value,Environment::keccak(with a customYulNamePairHashusingboost::hash_combine),Scope::variables,m_functionSideEffects→ unordered.m_offsets,m_lastKnownValue→ unordered.m_groupMembersleft asstd::map<YulName, std::set<YulName>>on purpose —*group->begin()picks the minimum representative, which must stay deterministic.std::map<FunctionHandle, SideEffects>→std::unordered_mapend-to-end (SideEffectsPropagator,SideEffectsCollector,MovableChecker,DataFlowAnalyzer,CommonSubexpressionEliminator,EqualStoreEliminator,LoadResolver,UnusedStoreEliminator,UnusedPruner,LoopInvariantCodeMotion, plusFunctionSideEffectstest). Same forstd::map<YulName, ControlFlowSideEffects>inControlFlowSideEffectsCollector::functionSideEffectsNamed()and all its consumers.ReferencesCounter::countReferences()andVariableReferencesCounter::countReferences()now returnstd::unordered_map; all call sites updated.Substitution,FunctionCopier::m_translations,NameSimplifier::m_translations,VarNameCleaner(m_namesToKeep,m_usedNames,m_translatedNames),SyntacticalEquality::m_identifiers{LHS,RHS},ExpressionJoiner::m_references,EquivalentFunction{Combiner,Detector}::m_duplicates,Disambiguator::m_translations,AssignmentCounter::m_assignmentCounters,Rematerialiser(m_referenceCounts,m_varsToAlwaysRematerialize),SSAValueTracker::ssaVariables()→ unordered.EVMDialect::m_reserved→std::unordered_setwith a transparent hasher (heterogeneousstring_viewlookup preserved),Object::{objectPaths, dataPaths, subIndexByName},CompilerContext::m_externallyUsedYulFunctions,Assembly::m_namedTags→ unordered. Localstd::set<YulName>inCodeTransform::assignedFunctionNamesandLoopInvariantCodeMotion::{ssaVars, varsDefinedInScope}→ unordered.count()+at()patterns inControlFlowSideEffectsCollector,ObjectOptimizer,Semantics::containsNonContinuingFunctionCall,Assembly::namedTag,NameSimplifier::findSimplification.Containers that require ordered iteration (
NameCollector::m_names,AssignmentsSinceContinue::m_names,assignedVariableNames(),KnowledgeBase::m_groupMembersinner set,IRGenerationContext::m_usedSourceNames,NameDispenser::usedNames()public API,OptimiserStepContext::reservedIdentifiers,OptimiserSuite::run(..., _externallyUsedIdentifiers)) are deliberately left untouched.Motivation
Profiling via-IR compilation of OpenZeppelin 5.0.2 shows
YulString::operator<at ~2.6% of samples andstd::__treeoperations onYulName/FunctionHandleat ~4.4% combined — pure overhead from using ordered containers for keys whose iteration order never mattered. Each lookup is O(log n) with O(key length) comparisons; unordered variants make it amortized O(1) with a single hash of the 64-bit handle.These are mechanical, behavior-preserving changes — the compiler works exactly the same way, it just uses its data structures more efficiently.
Benchmark (via-IR,
--optimize, 10 runs each, first run discarded as cold, median of 9)(Absolute times are lower than in the previous revision because this run was done on a cooler machine — the relative deltas are what matters. Compared to v1 of this PR, the second commit adds roughly -1.7 p.p. on OZ 5.0.2 and -7 p.p. on Uniswap V4 / OZ 4.9.0 — the new optimizations target DataFlowAnalyzer / KnowledgeBase / Semantics, which are on the hot path of every via-IR compilation rather than project-specific hot paths.)
Run-to-run spread is also tight (≤ 0.5 s on all three projects), consistent with eliminating cache-miss-heavy tree traversals.
Test plan
--metadata-hash noneon several OpenZeppelin contracts)-Werrorenabled)