Skip to content

Commit 2419a4d

Browse files
authored
Merge pull request #787 from ethpandaops/qu0b/shuffle-worker-pool
shuffle: conformance test, GOMAXPROCS-aware fan-out, fork-join cleanup
2 parents b2dab8c + 47ee5c9 commit 2419a4d

2 files changed

Lines changed: 47 additions & 23 deletions

File tree

indexer/beacon/statetransition/committees.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,10 @@ func computeShuffledList(indexCount uint64, seed phase0.Root, specs *consensus.C
155155
bucketCount := (indexCount + 255) / 256
156156
sources := make([]phase0.Root, bucketCount)
157157

158-
// Worker pool sized to the available cores; the per-round position updates
159-
// are independent and dominate the cost, so they are split across workers.
160-
workers := runtime.NumCPU()
161-
if workers < 1 {
162-
workers = 1
163-
}
158+
// Fan the per-round position updates out across the schedulable cores; they
159+
// are independent and dominate the cost. GOMAXPROCS (not NumCPU) so
160+
// container CPU quotas and user overrides are respected.
161+
workers := runtime.GOMAXPROCS(0)
164162

165163
for currentRound := uint64(0); currentRound < specs.ShuffleRoundCount; currentRound++ {
166164
// Compute pivot once per round (depends only on seed + round).
@@ -191,6 +189,10 @@ func computeShuffledList(indexCount uint64, seed phase0.Root, specs *consensus.C
191189

192190
// shuffleRound applies one swap-or-not round to result in place, dividing the
193191
// index range across workers goroutines (or running inline for small inputs).
192+
//
193+
// Spawning fresh goroutines per round beats a persistent channel-fed worker
194+
// pool here: the serial source-hash phase between rounds parks pool workers,
195+
// and waking them via channel sends measures slower than fresh spawns.
194196
func shuffleRound(result []uint64, sources []phase0.Root, pivot, indexCount uint64, workers int) {
195197
apply := func(start, end uint64) {
196198
for i := start; i < end; i++ {
@@ -217,20 +219,9 @@ func shuffleRound(result []uint64, sources []phase0.Root, pivot, indexCount uint
217219

218220
var wg sync.WaitGroup
219221
chunk := (n + uint64(workers) - 1) / uint64(workers)
220-
for w := 0; w < workers; w++ {
221-
start := uint64(w) * chunk
222-
end := start + chunk
223-
if end > n {
224-
end = n
225-
}
226-
if start >= end {
227-
break
228-
}
229-
wg.Add(1)
230-
go func(start, end uint64) {
231-
defer wg.Done()
232-
apply(start, end)
233-
}(start, end)
222+
for start := uint64(0); start < n; start += chunk {
223+
end := min(start+chunk, n)
224+
wg.Go(func() { apply(start, end) })
234225
}
235226
wg.Wait()
236227
}
@@ -277,9 +268,6 @@ func getCommitteeIndicesFromBits(committeeBits []byte) []uint64 {
277268
return indices
278269
}
279270

280-
// unused import guard
281-
var _ = binary.LittleEndian
282-
283271
// processSyncCommitteeUpdates rotates the sync committee at period boundaries.
284272
// New in Altair: https://github.com/ethereum/consensus-specs/blob/master/specs/altair/beacon-chain.md#sync-committee-updates
285273
func processSyncCommitteeUpdates(s *stateAccessor) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package statetransition
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ethpandaops/dora/clients/consensus"
7+
"github.com/ethpandaops/go-eth2-client/spec/phase0"
8+
)
9+
10+
// TestComputeShuffledListMatchesPerIndex verifies the batch shuffle against the
11+
// spec-reference per-index compute_shuffled_index for sizes below and above the
12+
// parallelization threshold.
13+
func TestComputeShuffledListMatchesPerIndex(t *testing.T) {
14+
specs := &consensus.ChainSpec{ChainSpecPreset: consensus.ChainSpecPreset{ShuffleRoundCount: 90}}
15+
seed := phase0.Root{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc}
16+
17+
for _, n := range []uint64{0, 1, 2, 255, 256, 257, 4095, 4096, 20000} {
18+
list := computeShuffledList(n, seed, specs)
19+
if uint64(len(list)) != n {
20+
t.Fatalf("n=%d: got list of length %d", n, len(list))
21+
}
22+
for i := uint64(0); i < n; i++ {
23+
if want := computeShuffledIndex(i, n, seed, specs); list[i] != want {
24+
t.Fatalf("n=%d: list[%d] = %d, want %d", n, i, list[i], want)
25+
}
26+
}
27+
}
28+
}
29+
30+
func BenchmarkComputeShuffledList(b *testing.B) {
31+
specs := &consensus.ChainSpec{ChainSpecPreset: consensus.ChainSpecPreset{ShuffleRoundCount: 90}}
32+
seed := phase0.Root{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc}
33+
for b.Loop() {
34+
computeShuffledList(1_000_000, seed, specs)
35+
}
36+
}

0 commit comments

Comments
 (0)