From e7f224651c20affba1bf21d6aeecfccd8bcc96dd Mon Sep 17 00:00:00 2001 From: Bruce Ma Date: Tue, 6 Aug 2019 20:52:47 +0800 Subject: [PATCH] some refactors on shuffle sharding Signed-off-by: Bruce Ma --- .../util/shufflesharding/shufflesharding.go | 63 +++++----- .../shufflesharding/shufflesharding_test.go | 114 ++++++++---------- 2 files changed, 79 insertions(+), 98 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding.go b/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding.go index 39bfa60239c..a95d791f6e7 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding.go +++ b/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding.go @@ -24,27 +24,27 @@ import ( const maxHashBits = 60 // ValidateParameters can validate parameters for shuffle sharding -// in a fast but approximate way, including numQueues and handSize -// Algorithm: maxHashValue >= numQueues^handSize -func ValidateParameters(numQueues, handSize int32) bool { - if handSize <= 0 || numQueues <= 0 || handSize > numQueues { +// in a fast but approximate way, including deckSize and handSize +// Algorithm: maxHashBits >= bits(deckSize^handSize) +func ValidateParameters(deckSize, handSize int32) bool { + if handSize <= 0 || deckSize <= 0 || handSize > deckSize { return false } - return math.Log2(float64(numQueues))*float64(handSize) <= maxHashBits + return math.Log2(float64(deckSize))*float64(handSize) <= maxHashBits } -// Deal can shuffle a hash value to handSize-quantity and non-redundant -// indices of queue, with the pick function, we can get the optimal queue index -// Eg. From numQueues=128, handSize=8, we can get an index array [12 14 73 18 119 51 117 26], +// ShuffleAndDeal can shuffle a hash value to handSize-quantity and non-redundant +// indices of decks, with the pick function, we can get the optimal deck index +// Eg. From deckSize=128, handSize=8, we can get an index array [12 14 73 18 119 51 117 26], // then pick function will choose the optimal index from these // Algorithm: https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190228-priority-and-fairness.md#queue-assignment-proof-of-concept -func Deal(hashValue uint64, numQueues, handSize int32, pick func(int32) error) error { +func ShuffleAndDeal(hashValue uint64, deckSize, handSize int32, pick func(int32)) { remainders := make([]int32, handSize) for i := int32(0); i < handSize; i++ { - hashValueNext := hashValue / uint64(numQueues-i) - remainders[i] = int32(hashValue - uint64(numQueues-i)*hashValueNext) + hashValueNext := hashValue / uint64(deckSize-i) + remainders[i] = int32(hashValue - uint64(deckSize-i)*hashValueNext) hashValue = hashValueNext } @@ -55,42 +55,43 @@ func Deal(hashValue uint64, numQueues, handSize int32, pick func(int32) error) e candidate++ } } - if err := pick(candidate); err != nil { - return err - } + pick(candidate) } - - return nil } -// DealWithValidation will do validation before Deal -func DealWithValidation(hashValue uint64, numQueues, handSize int32, pick func(int32) error) error { - if !ValidateParameters(numQueues, handSize) { +// ShuffleAndDealWithValidation will do validation before ShuffleAndDeal +func ShuffleAndDealWithValidation(hashValue uint64, deckSize, handSize int32, pick func(int32)) error { + if !ValidateParameters(deckSize, handSize) { return errors.New("bad parameters") } - return Deal(hashValue, numQueues, handSize, pick) + ShuffleAndDeal(hashValue, deckSize, handSize, pick) + return nil } -// DealToSlice will use specific pick function to return slices of indices -// after Deal -func DealToSlice(hashValue uint64, numQueues, handSize int32) ([]int32, error) { - if !ValidateParameters(numQueues, handSize) { - return nil, errors.New("bad parameters") - } - +// ShuffleAndDealToSlice will use specific pick function to return slices of indices +// after ShuffleAndDeal +func ShuffleAndDealToSlice(hashValue uint64, deckSize, handSize int32) []int32 { var ( candidates = make([]int32, handSize) idx = 0 ) - pickToSlices := func(can int32) error { + pickToSlices := func(can int32) { candidates[idx] = can idx++ - return nil } - _ = Deal(hashValue, numQueues, handSize, pickToSlices) + ShuffleAndDeal(hashValue, deckSize, handSize, pickToSlices) - return candidates, nil + return candidates +} + +// ShuffleAndDealToSliceWithValidation will do validation before ShuffleAndDealToSlice +func ShuffleAndDealToSliceWithValidation(hashValue uint64, deckSize, handSize int32) ([]int32, error) { + if !ValidateParameters(deckSize, handSize) { + return nil, errors.New("bad parameters") + } + + return ShuffleAndDealToSlice(hashValue, deckSize, handSize), nil } diff --git a/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding_test.go b/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding_test.go index 4b99b95b08f..b790842e175 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/shufflesharding/shufflesharding_test.go @@ -17,7 +17,6 @@ limitations under the License. package shufflesharding import ( - "errors" "math/rand" "testing" ) @@ -25,12 +24,12 @@ import ( func TestValidateParameters(t *testing.T) { tests := []struct { name string - numQueues int32 + deckSize int32 handSize int32 validated bool }{ { - "numQueues is < 0", + "deckSize is < 0", -100, 8, false, @@ -42,7 +41,7 @@ func TestValidateParameters(t *testing.T) { false, }, { - "numQueues is 0", + "deckSize is 0", 0, 8, false, @@ -54,25 +53,25 @@ func TestValidateParameters(t *testing.T) { false, }, { - "handSize is greater than numQueues", + "handSize is greater than deckSize", 128, 129, false, }, { - "numQueues: 128 handSize: 6", + "deckSize: 128 handSize: 6", 128, 6, true, }, { - "numQueues: 1024 handSize: 6", + "deckSize: 1024 handSize: 6", 1024, 6, true, }, { - "numQueues: 512 handSize: 8", + "deckSize: 512 handSize: 8", 512, 8, false, @@ -80,7 +79,7 @@ func TestValidateParameters(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if ValidateParameters(test.numQueues, test.handSize) != test.validated { + if ValidateParameters(test.deckSize, test.handSize) != test.validated { t.Errorf("test case %s fails", test.name) return } @@ -89,105 +88,80 @@ func TestValidateParameters(t *testing.T) { } func BenchmarkValidateParameters(b *testing.B) { - queueSize, handSize := int32(512), int32(8) + deckSize, handSize := int32(512), int32(8) for i := 0; i < b.N; i++ { - _ = ValidateParameters(queueSize, handSize) + _ = ValidateParameters(deckSize, handSize) } } -func TestDealWithValidation(t *testing.T) { +func TestShuffleAndDealWithValidation(t *testing.T) { tests := []struct { name string - numQueues int32 + deckSize int32 handSize int32 - pick func(int32) error + pick func(int32) validated bool }{ { - "numQueues is < 0", + "deckSize is < 0", -100, 8, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, { "handSize is < 0", 128, -100, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, { - "numQueues is 0", + "deckSize is 0", 0, 8, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, { "handSize is 0", 128, 0, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, { - "handSize is greater than numQueues", + "handSize is greater than deckSize", 128, 129, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, { - "numQueues: 128 handSize: 6", + "deckSize: 128 handSize: 6", 128, 6, - func(i int32) error { - return nil - }, + func(int32) {}, true, }, { - "numQueues: 1024 handSize: 6", + "deckSize: 1024 handSize: 6", 1024, 6, - func(i int32) error { - return nil - }, + func(int32) {}, true, }, { - "numQueues: 128 handSize: 6 with bad pick", - 128, - 6, - func(i int32) error { - return errors.New("for test") - }, - false, - }, - { - "numQueues: 512 handSize: 8", + "deckSize: 512 handSize: 8", 512, 8, - func(i int32) error { - return nil - }, + func(int32) {}, false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if (DealWithValidation(rand.Uint64(), test.numQueues, test.handSize, test.pick) == nil) != test.validated { + if (ShuffleAndDealWithValidation(rand.Uint64(), test.deckSize, test.handSize, test.pick) == nil) != test.validated { t.Errorf("test case %s fails", test.name) return } @@ -195,21 +169,19 @@ func TestDealWithValidation(t *testing.T) { } } -func BenchmarkDeal(b *testing.B) { +func BenchmarkShuffleAndDeal(b *testing.B) { hashValue := rand.Uint64() - queueSize, handSize := int32(512), int32(8) - pick := func(int32) error { - return nil - } + deckSize, handSize := int32(512), int32(8) + pick := func(int32) {} for i := 0; i < b.N; i++ { - _ = Deal(hashValue, queueSize, handSize, pick) + ShuffleAndDeal(hashValue, deckSize, handSize, pick) } } -func TestDealToSlices(t *testing.T) { +func TestShuffleAndDealToSliceWithValidation(t *testing.T) { tests := []struct { name string - numQueues int32 + deckSize int32 handSize int32 validated bool }{ @@ -220,25 +192,25 @@ func TestDealToSlices(t *testing.T) { false, }, { - "numQueues == handSize == 4", + "deckSize == handSize == 4", 4, 4, true, }, { - "numQueues == handSize == 8", + "deckSize == handSize == 8", 8, 8, true, }, { - "numQueues == handSize == 10", + "deckSize == handSize == 10", 10, 10, true, }, { - "numQueues == handSize == 12", + "deckSize == handSize == 12", 12, 12, true, @@ -247,7 +219,7 @@ func TestDealToSlices(t *testing.T) { for _, test := range tests { hashValue := rand.Uint64() t.Run(test.name, func(t *testing.T) { - cards, err := DealToSlice(hashValue, test.numQueues, test.handSize) + cards, err := ShuffleAndDealToSliceWithValidation(hashValue, test.deckSize, test.handSize) if (err == nil) != test.validated { t.Errorf("test case %s fails in validation check", test.name) return @@ -275,3 +247,11 @@ func TestDealToSlices(t *testing.T) { }) } } + +func BenchmarkShuffleAndDealToSlice(b *testing.B) { + hashValue := rand.Uint64() + deckSize, handSize := int32(512), int32(8) + for i := 0; i < b.N; i++ { + _ = ShuffleAndDealToSlice(hashValue, deckSize, handSize) + } +}