From ae4be1be14c328ea55e49fa7a83fd5f19c227a9a Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Thu, 17 Sep 2026 15:49:43 +0000 Subject: [PATCH] Use inline small storage for small SemIR ID sets in toolchain (#7796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply SmallSize = 16 to frequent identifier and instruction/function sets in ScopeStack, Class, FacetType, and SpecificCoalescer to avoid dynamic heap allocations on small scopes. Also defer dest_field_names set allocation in struct conversion and provide default KeyContext for SetBase. This was found by inspection, but does seem to be a clear 1.5% win on overall compile time. ``` Ran baseline and experiment 10 times on 128 x 2450 MHz CPUs CPU caches: L1 Data 32Ki L1 Instruction 32Ki L2 Unified 512Ki L3 Unified 32Mi Load avg: 1.2041 1.16895 2.86133 Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05 Metric key: BenchmarkName... 👍 p= baseline: ± <% at 95th conf> experiment: ± <% at 95th conf> Benchmark ┃ CPU Time ┃ CYCLES ┃ INSTRUCTIONS ┃ Lines ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━ BM_CompileApiFileDenseDecls/256... │ 👍 -1.768% p=0.0346 │ 👍 -1.683% p=0.0346 │ 👍 0.160% p=0.000428 │ ~ baseline: │ 49.11 ms ± 1.363% │ 155.6 M ± 1.875% │ 293.9 M ± 0.015% │ 4.093 k ± 1.360% experiment: │ 48.24 ms ± 2.213% │ 153 M ± 2.465% │ 293.4 M ± 0.081% │ 4.166 k ± 2.165% │ │ │ │ BM_CompileApiFileDenseDecls/1024.. │ ?? p=0.159 │ ?? p=0.067 │ 👍 0.153% p=0.000249 │ ~ baseline: │ 50.61 ms ± 2.379% │ 160.7 M ± 2.777% │ 309.7 M ± 0.015% │ 19.96 k ± 2.326% experiment: │ 50.32 ms ± 0.971% │ 159.6 M ± 0.787% │ 309.2 M ± 0.086% │ 20.07 k ± 0.980% │ │ │ │ BM_CompileApiFileDenseDecls/4096.. │ 👍 -1.593% p=0.0112 │ 👍 -1.632% p=0.00743 │ 👍 0.138% p=0.000328 │ ~ baseline: │ 58.58 ms ± 1.673% │ 186.5 M ± 1.487% │ 371.2 M ± 0.102% │ 70.96 k ± 1.645% experiment: │ 57.65 ms ± 1.032% │ 183.5 M ± 1.079% │ 370.7 M ± 0.091% │ 72.11 k ± 1.043% │ │ │ │ BM_CompileApiFileDenseDecls/16384. │ 👍 -1.695% p=0.029 │ 👍 -1.823% p=0.0411 │ 👍 0.221% p=0.000428 │ ~ baseline: │ 90.07 ms ± 3.686% │ 287.1 M ± 3.694% │ 618.9 M ± 0.106% │ 186.9 k ± 3.555% experiment: │ 88.55 ms ± 1.891% │ 281.8 M ± 2.142% │ 617.6 M ± 0.158% │ 190.1 k ± 1.856% │ │ │ │ BM_CompileApiFileDenseDecls/65536. │ 👍 -1.797% p=0.0201 │ 👍 -1.762% p=0.0201 │ 👍 0.222% p=0.000328 │ ~ baseline: │ 222.1 ms ± 2.560% │ 711 M ± 2.417% │ 1.613 G ± 0.158% │ 304.2 k ± 2.496% experiment: │ 218.1 ms ± 1.950% │ 698.5 M ± 2.010% │ 1.61 G ± 0.075% │ 309.7 k ± 1.989% │ │ │ │ BM_CompileApiFileDenseDecls/262144 │ ?? p=0.398 │ ?? p=0.36 │ 👍 0.166% p=0.000931 │ ~ baseline: │ 782.2 ms ± 2.666% │ 2.502 G ± 2.546% │ 5.596 G ± 0.038% │ 345.8 k ± 2.597% experiment: │ 780.6 ms ± 4.249% │ 2.483 G ± 4.691% │ 5.587 G ± 0.024% │ 346.5 k ± 4.075% │ │ │ │ ``` Assisted-by: Antigravity with Gemini --- common/set.h | 2 +- toolchain/check/class.cpp | 2 +- toolchain/check/convert.cpp | 10 +++++----- toolchain/check/facet_type.cpp | 2 +- toolchain/check/scope_stack.h | 2 +- toolchain/lower/BUILD | 1 + toolchain/lower/specific_coalescer.cpp | 8 ++++---- toolchain/lower/specific_coalescer.h | 9 +++++---- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/common/set.h b/common/set.h index f0b7b4976612..7d1c3d8b42eb 100644 --- a/common/set.h +++ b/common/set.h @@ -131,7 +131,7 @@ class SetView : RawHashtable::ViewImpl { // A pointer or reference to this type is the preferred way to pass a mutable // handle to a `Set` type across API boundaries as it avoids encoding specific // SSO sizing information while providing a near-complete mutable API. -template +template class SetBase : protected RawHashtable::BaseImpl { protected: diff --git a/toolchain/check/class.cpp b/toolchain/check/class.cpp index 1c74cb2cab03..3c1df1a6c161 100644 --- a/toolchain/check/class.cpp +++ b/toolchain/check/class.cpp @@ -222,7 +222,7 @@ static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id, }; llvm::SmallVector vtable; - Set implemented_impls; + Set implemented_impls; bool carbon_native_vtable = true; // Add vtable entries from the base class, updating them to point to a derived diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 171a4fb49f7d..e2535eb23a70 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -649,15 +649,15 @@ static auto ConvertStructToStructOrClass( value_id = MaterializeIfInitializer(context, value_id); } - Set dest_field_names; - for (auto field : dest_elem_fields) { - dest_field_names.Insert(field.name_id); - } - // Prepare to look up fields in the source by index. Also check for // source fields that don't match any field in the destination. Map src_field_indexes; if (src_type.fields_id != dest_type.fields_id) { + Set dest_field_names; + for (auto field : dest_elem_fields) { + dest_field_names.Insert(field.name_id); + } + for (auto [i, field] : llvm::enumerate(src_elem_fields)) { if (!dest_field_names.Lookup(field.name_id)) { if (target.diagnose) { diff --git a/toolchain/check/facet_type.cpp b/toolchain/check/facet_type.cpp index 39f590299a10..2e14d63ddc5a 100644 --- a/toolchain/check/facet_type.cpp +++ b/toolchain/check/facet_type.cpp @@ -550,7 +550,7 @@ auto FindWhere(Context& context, SemIR::ConstantId const_id) -> bool { private: bool* found_; - Set searched_; + Set searched_; }; if (!const_id.is_constant()) { diff --git a/toolchain/check/scope_stack.h b/toolchain/check/scope_stack.h index 07c12991ee25..ac4585bb71d2 100644 --- a/toolchain/check/scope_stack.h +++ b/toolchain/check/scope_stack.h @@ -359,7 +359,7 @@ class ScopeStack { // Names which are registered with lexical_lookup_, and will need to be // unregistered when the scope ends. - Set names = {}; + Set names = {}; }; // A scope in which `return` can be used. diff --git a/toolchain/lower/BUILD b/toolchain/lower/BUILD index e4537d4763c4..e119cbb03b54 100644 --- a/toolchain/lower/BUILD +++ b/toolchain/lower/BUILD @@ -69,6 +69,7 @@ cc_library( "//common:map", "//common:pretty_stack_trace_function", "//common:raw_string_ostream", + "//common:set", "//common:vlog", "//toolchain/base:fixed_size_value_store", "//toolchain/base:kind_switch", diff --git a/toolchain/lower/specific_coalescer.cpp b/toolchain/lower/specific_coalescer.cpp index 553a86313595..40582bc232a4 100644 --- a/toolchain/lower/specific_coalescer.cpp +++ b/toolchain/lower/specific_coalescer.cpp @@ -59,7 +59,7 @@ auto SpecificCoalescer::CoalesceEquivalentSpecifics( continue; } - Set> + Set, 16> visited_equivalent_specifics; InsertPair(specifics[i], specifics[j], visited_equivalent_specifics); // Function type information matches; check usages inside the function @@ -205,7 +205,7 @@ auto SpecificCoalescer::AreFunctionTypesEquivalent( auto SpecificCoalescer::AreFunctionBodiesEquivalent( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - Set>& + SetBase>& visited_equivalent_specifics) -> bool { llvm::SmallVector> worklist; worklist.push_back({specific_id1, specific_id2}); @@ -251,7 +251,7 @@ auto SpecificCoalescer::AreFunctionBodiesEquivalent( auto SpecificCoalescer::InsertPair( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - Set>& set_of_pairs) + SetBase>& set_of_pairs) -> bool { if (specific_id1.index > specific_id2.index) { std::swap(specific_id1.index, specific_id2.index); @@ -263,7 +263,7 @@ auto SpecificCoalescer::InsertPair( auto SpecificCoalescer::ContainsPair( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - const Set>& set_of_pairs) + SetView> set_of_pairs) -> bool { if (specific_id1.index > specific_id2.index) { std::swap(specific_id1.index, specific_id2.index); diff --git a/toolchain/lower/specific_coalescer.h b/toolchain/lower/specific_coalescer.h index 7f627b43b04a..c8a716b0a73b 100644 --- a/toolchain/lower/specific_coalescer.h +++ b/toolchain/lower/specific_coalescer.h @@ -5,6 +5,7 @@ #ifndef CARBON_TOOLCHAIN_LOWER_SPECIFIC_COALESCER_H_ #define CARBON_TOOLCHAIN_LOWER_SPECIFIC_COALESCER_H_ +#include "common/set.h" #include "llvm/Support/BLAKE3.h" #include "toolchain/lower/context.h" #include "toolchain/sem_ir/ids.h" @@ -87,7 +88,7 @@ class SpecificCoalescer { // found, if the two specifics given as arguments are found to be equivalent. auto AreFunctionBodiesEquivalent( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - Set>& + SetBase>& visited_equivalent_specifics) -> bool; // Given an equivalent pair of specifics, updates the canonical specific to @@ -118,13 +119,13 @@ class SpecificCoalescer { // checks entry already existed if it cannot be inserted. auto InsertPair( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - Set>& set_of_pairs) + SetBase>& set_of_pairs) -> bool; // Checks if a pair is contained into a set of pairs, in canonical form. auto ContainsPair( SemIR::SpecificId specific_id1, SemIR::SpecificId specific_id2, - const Set>& set_of_pairs) + SetView> set_of_pairs) -> bool; // The optional vlog stream. @@ -154,7 +155,7 @@ class SpecificCoalescer { // Non-equivalent specifics found. // TODO: Revisit this due to its quadratic space growth. - Set> + Set, 16> non_equivalent_specifics_; };