Skip to content

Commit 56d811b

Browse files
yfeldblummeta-codesync[bot]
authored andcommitted
chunk the loop in hazptr_domain::load_hazptr_vals
Summary: In `hazptr_domain::load_hazptr_vals` there is a loop over the hprecs array. When the hprecs array is small, the cost is immaterial. When it is large but sparse, there are two costs: * The loop-carried dependency on the loop counter. The loop body is too large for automatic unrolling. * The interleaving of the hazard pointer loads and the branches, which check whether the loaded values is null. Reduce both costs by * Chunking the loop. Use `kNumShards` for this. * In each chunk, loading all of the hzard pointers first and then checking them all. Reviewed By: DenisYaroshevskiy Differential Revision: D88280358 fbshipit-source-id: 432f8fef7a0d3fa5414b792eb6bd26682c14b2f5
1 parent 7868b4b commit 56d811b

1 file changed

Lines changed: 30 additions & 4 deletions

File tree

third-party/folly/src/folly/synchronization/HazptrDomain.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,10 +479,36 @@ class hazptr_domain {
479479
Set hs;
480480
auto sz = std::max(0, hcount_.load(std::memory_order_relaxed));
481481
if (auto* hprecs = hprecs_.load(std::memory_order_acquire)) {
482-
for (auto hprec : hprecs->as_ptr_span(size_t(sz))) {
483-
constexpr auto order = kIsSanitizeThread
484-
? std::memory_order_acquire // tsan does not instrument fences
485-
: std::memory_order_relaxed; // fence below provides acquire order
482+
// chunk the loop to avoid a single loop-carried dependency on the loop
483+
// counter; helpful when the hprecs array is large but sparse
484+
constexpr size_t chunk_width = kNumShards;
485+
constexpr auto order = kIsSanitizeThread
486+
? std::memory_order_acquire // tsan does not instrument fences
487+
: std::memory_order_relaxed; // fence below provides acquire order
488+
auto ptrspan = hprecs->as_ptr_span(size_t(sz));
489+
for (size_t i = 0; i + chunk_width <= ptrspan.size(); i += chunk_width) {
490+
// load a batch of hazard pointers up-front so that the branches below
491+
// can run in parallel with each other on x86
492+
const void* ptrs[chunk_width];
493+
for (size_t j = 0; j < chunk_width; ++j) {
494+
auto hprec = ptrspan[i + j];
495+
ptrs[j] = hprec->hazptr(order);
496+
}
497+
// when the hprecs array is sparse, the branches in this loop can run in
498+
// parallel with each other on x86 since they are not blocked on loads
499+
// from memory or cache; the loads are served either from registers or
500+
// from the store buffer and do not block each other
501+
for (auto ptr : ptrs) {
502+
if (ptr) {
503+
hs.insert(ptr);
504+
}
505+
}
506+
}
507+
// final undersized chunk; keep it here, rather than blending into the
508+
// chunked loop above, to minimize the number of instructions executed in
509+
// the main loop body
510+
ptrspan = ptrspan.subspan(ptrspan.size() & ~(chunk_width - 1));
511+
for (auto hprec : ptrspan) {
486512
if (auto ptr = hprec->hazptr(order)) {
487513
hs.insert(ptr);
488514
}

0 commit comments

Comments
 (0)