Skip to content

Use multiply-high range reduction for probing - #869

Merged
sleeepyjack merged 7 commits into
NVIDIA:devfrom
sleeepyjack:mulhi-reduction-2
Oct 31, 2026
Merged

sleeepyjack merged 7 commits into
NVIDIA:devfrom
sleeepyjack:mulhi-reduction-2

Conversation

@sleeepyjack

@sleeepyjack sleeepyjack commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Modulo by a runtime bucket count is expensive, and probing does not need the exact remainder: it only needs a well-distributed index in [0, n). This uses multiply-high instead of modulo for initial bucket selection and double-hashing steps.

For a uniform w-bit hash, both h % n and floor(h * n / 2^w) assign either floor(2^w / n) or ceil(2^w / n) hash values to each bucket. They provide the same range and bucket balance, but assign individual keys to different buckets.

Codegen

Whole-kernel static SASS instruction counts on CUDA 13.4, sm_120:

Kernel Modulo Multiply-high
Index-mapping test, 32-bit hash / 64-bit index 96 20
Map find, 32-bit keys 236 162
Map find, 64-bit keys 239 165

Counts include setup and cold paths, excluding NOP padding; they are not instructions executed per lookup. The index-mapping test also drops from 18 to 12 registers.

Performance

RTX PRO 6000 Blackwell Max-Q, CUDA 13.4, GCC 14, Release, sm_120. Default XXH32, 32/64-bit keys, 800K/8M inputs. Three paired runs against 27302294; negative is faster. Summary values are geometric-mean GPU time changes.

Each delta compares multiply-high against modulo with the same probing scheme, not linear probing against double hashing.

Operation Probing Time change
Map insert Linear -3.76%
Map find Linear -3.24%
Map insert Double hashing -0.71%
Map find Double hashing -3.29%
Multimap insert Double hashing -3.46%
Multimap count Double hashing -4.53%

Maps use their default CG=4 and bucket size 1 for both probing schemes. Multimaps use their defaults: double hashing, CG=8, bucket size 2. All extents are 64-bit.

Map: 800K keys

LP = linear probing, DH = double hashing. Per-configuration GPU time changes use the median of three runs. Find columns use either all hits or all misses.

Key width Occupancy LP insert DH insert LP find hits DH find hits LP find misses DH find misses
32-bit 50% -0.30% -0.58% -2.01% -1.16% -3.20% -4.01%
32-bit 80% -9.08% -0.31% -6.44% -2.13% -3.53% -3.57%
32-bit 90% -3.43% -1.73% +15.81% -8.33% +6.37% -2.78%
64-bit 50% +0.26% -1.06% -0.06% -1.46% -1.26% -4.18%
64-bit 80% -9.99% -0.09% -17.57% -2.47% -7.48% -3.12%
64-bit 90% -20.56% -0.90% -17.21% -3.35% -11.58% -2.08%

Map: 8M keys

Key width Occupancy LP insert DH insert LP find hits DH find hits LP find misses DH find misses
32-bit 50% -0.03% -0.21% -1.35% -3.12% -3.15% -4.94%
32-bit 80% -1.37% -1.59% -2.56% -7.05% -2.68% -4.84%
32-bit 90% +0.25% -1.90% -4.61% -6.82% -2.28% -4.75%
64-bit 50% -0.09% -0.05% +0.01% -0.19% -0.45% -0.13%
64-bit 80% -0.03% -0.06% -1.83% -1.62% -1.61% -2.00%
64-bit 90% +1.95% -0.08% -3.06% -2.00% -0.86% -2.29%

The 800K/I32/90% linear-probing lookup regression comes primarily from longer probe tails in the changed table layout, as confirmed by profiling and identical-code replay. It does not appear in this double-hashing sweep. Differences around 1% should be treated cautiously.

@sleeepyjack sleeepyjack self-assigned this Oct 29, 2026
@sleeepyjack sleeepyjack added the topic: performance Performance related issue label Oct 29, 2026
@sleeepyjack
sleeepyjack marked this pull request as ready for review October 29, 2026 23:56
@sleeepyjack sleeepyjack added the type: improvement Improvement / enhancement to an existing function label Oct 29, 2026
Comment thread include/cuco/detail/utils.cuh Outdated
Comment on lines +51 to +55
if constexpr (cuda::std::is_same_v<HashType, cuda::std::array<std::uint64_t, 2>>) {
#if !defined(CUCO_HAS_INT128)
static_assert(false,
"CUCO_HAS_INT128 undefined. Need unsigned __int128 type when sanitizing "
"cuda::std::array<std::uint64_t, 2>");
#endif
unsigned __int128 ret{};
memcpy(&ret, &hash, sizeof(unsigned __int128));
magnitude = to_positive<SizeType>(ret);
return hash_to_index<SizeType>(static_cast<unsigned_size_type>(hash[0]), upper_bound);
} else if constexpr (hash_bytes > 8) {
static_assert(cuda::std::is_integral_v<HashType> and hash_bytes == 16);
return hash_to_index<SizeType>(static_cast<unsigned_size_type>(hash), upper_bound);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: cudax (NVIDIA/cccl#12360) uses the high word of a 128-bit hash, while this uses the low word. Either works, but let's make them match.

@PointKernel

Copy link
Copy Markdown
Member

The +15.81% slowdown for LP find hits at 800K/I32/90% is surprising, since modulo and multiply-high spread a good hash equally well. Does it show up with a different key seed?

@sleeepyjack

Copy link
Copy Markdown
Collaborator Author

The +15.81% slowdown for LP find hits at 800K/I32/90% is surprising, since modulo and multiply-high spread a good hash equally well. Does it show up with a different key seed?

The slowdown is an artifact. I ran a number of different seeds and none of them showed this level of slowdown. Note that this particular configuration (very high load factor with linear probing) is prone to having a handful of probe chains that are exceptionally long and hold the kernel hostage until they finish. No one should be using LP at >70% LF anyways.

Match cudax::cuco (NVIDIA/cccl#12360): a 128-bit hash contributes its high 64 bits, which are then reduced like a 64-bit hash.
@sleeepyjack
sleeepyjack merged commit 313ecd7 into NVIDIA:dev Oct 31, 2026
24 checks passed
@PointKernel

Copy link
Copy Markdown
Member

No one should be using LP at >70% LF anyways.

lol, that's fair.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: performance Performance related issue type: improvement Improvement / enhancement to an existing function

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants