[llvm] [ADT] Exit doFind on an empty map, not just an unallocated one (PR #220294)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 1 09:48:53 PDT 2026
https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/220294
`doFind` exits early only when no buckets were ever allocated. A map that
had entries and lost them keeps its bucket array. `NumBuckets` is then not
zero, so every lookup hashes the key and probes.
For a `SmallDenseMap` in small mode, `NumBuckets` is the template parameter
`InlineBuckets`, a nonzero constant. The existing check can never fire for
those maps. An empty one hashes and probes on every lookup.
`getNumEntries() == 0` covers both cases. It also subsumes the old check.
There are no entries without buckets, so `Mask = NumBuckets - 1` is still
safe. It is one test either way, so non-empty lookups are unchanged.
| workload | instructions:u |
|---|---:|
| clang compiling 60 LLVM/Clang/MLIR translation units | **-0.133%** |
| `mlir-opt` canonicalize/cse over 1060 `mlir/test` files >4KB | **-0.277%** |
Three interleaved runs each. Ranges do not overlap. Exit codes are
identical on all 1448 candidate `mlir/test` files.
Adding `LLVM_UNLIKELY` to the branch is a regression, not a win. It takes
clang from -0.133% to -0.007%. Once this is the first check, an empty map
is the common case, not the rare one.
>From 8660322cbc20c4fcc80b6a8b674222984f27adbe Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 1 Sep 2026 09:30:31 -0700
Subject: [PATCH] [ADT] Exit doFind on an empty map, not just an unallocated
one
doFind returns early only when no buckets were ever allocated. A map that
had entries and lost them keeps its bucket array, so NumBuckets != 0 and
every lookup still hashes the key and probes. SmallDenseMap in small mode
never has NumBuckets == 0 at all -- getRep() returns the constant
InlineBuckets -- so an empty one always hashes and probes.
getNumEntries() == 0 covers both and subsumes the old check: there are no
entries without buckets, so reaching Mask = NumBuckets - 1 still implies
NumBuckets != 0. It is one test either way, so non-empty lookups are
unchanged.
instructions:u, three interleaved runs each, ranges non-overlapping:
-0.133% clang compiling 60 LLVM/Clang/MLIR translation units
-0.277% mlir-opt --verify-each=true --allow-unregistered-dialect
-pass-pipeline='builtin.module(canonicalize,cse,canonicalize,cse,canonicalize)'
over the 1060 files in mlir/test larger than 4KB that this
pipeline accepts
Exit codes are identical on all 1448 candidate mlir/test files.
Marking the branch LLVM_UNLIKELY is a regression, not a win: it takes the
clang figure from -0.133% to -0.007%, because once this is the first check
an empty map is the common outcome rather than the rare one.
---
llvm/include/llvm/ADT/DenseMap.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index 6d036d4a0729f..c0b5854ef09c9 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -722,7 +722,10 @@ class DenseMapBase : public DebugEpochBase {
template <typename LookupKeyT>
const BucketT *doFind(const LookupKeyT &Val) const {
auto [BucketsPtr, U, NumBuckets] = getRep();
- if (NumBuckets == 0)
+ // Subsumes the NumBuckets == 0 case: there are no entries without
+ // buckets, so a non-zero entry count implies NumBuckets != 0 and the
+ // Mask below is safe.
+ if (getNumEntries() == 0)
return nullptr;
const unsigned Mask = NumBuckets - 1;
More information about the llvm-commits
mailing list