diff --git a/hrw.go b/hrw.go index 07087df..8ceae9e 100644 --- a/hrw.go +++ b/hrw.go @@ -83,6 +83,17 @@ func SortSliceByValue(slice interface{}, hash uint64) { } } +// SortHasherSliceByValue receives []Hasher and hash to sort by value-distance. +func SortHasherSliceByValue[T Hasher](slice []T, hash uint64) { + rule := prepareHasherRule(slice) + if rule != nil { + swap := func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + } + sortByDistance(len(rule), false, rule, hash, swap) + } +} + // SortSliceByWeightValue received []T, weights and hash to sort by value-distance * weights func SortSliceByWeightValue(slice interface{}, weights []float64, hash uint64) { rule := prepareRule(slice) @@ -92,6 +103,17 @@ func SortSliceByWeightValue(slice interface{}, weights []float64, hash uint64) { } } +// SortHasherSliceByWeightValue receives []Hasher, weights and hash to sort by value-distance * weights. +func SortHasherSliceByWeightValue[T Hasher](slice []T, weights []float64, hash uint64) { + rule := prepareHasherRule(slice) + if rule != nil { + swap := func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + } + sortByWeight(len(slice), false, rule, weights, hash, swap) + } +} + // SortSliceByIndex received []T and hash to sort by index-distance func SortSliceByIndex(slice interface{}, hash uint64) { length := reflect.ValueOf(slice).Len() @@ -199,6 +221,18 @@ func prepareRule(slice interface{}) []uint64 { return rule } +func prepareHasherRule[T Hasher](hashers []T) []uint64 { + length := len(hashers) + if length == 0 { + return nil + } + result := make([]uint64, length) + for i := 0; i < length; i++ { + result[i] = hashers[i].Hash() + } + return result +} + // ValidateWeights checks if weights are normalized between 0.0 and 1.0 func ValidateWeights(weights []float64) error { for i := range weights { diff --git a/hrw_test.go b/hrw_test.go index 5173572..7eb3d7e 100644 --- a/hrw_test.go +++ b/hrw_test.go @@ -131,6 +131,23 @@ func TestSortSliceByValueHasher(t *testing.T) { require.Equal(t, expect, actual) } +func TestSortHasherSliceByValue(t *testing.T) { + actual := []hashString{"a", "b", "c", "d", "e", "f"} + expect := []hashString{"d", "f", "c", "b", "a", "e"} + hash := Hash(testKey) + SortHasherSliceByValue(actual, hash) + require.EqualValues(t, expect, actual) +} + +func TestSortHasherSliceByWeightValue(t *testing.T) { + actual := []hashString{"a", "b", "c", "d", "e", "f"} + weights := []float64{1.0, 1.0, 1.0, 1.0, 1.0, 1.0} + expect := []hashString{"d", "f", "c", "b", "a", "e"} + hash := Hash(testKey) + SortHasherSliceByWeightValue(actual, weights, hash) + require.EqualValues(t, expect, actual) +} + func TestSortSliceByValueIntSlice(t *testing.T) { cases := []slices{ { @@ -636,6 +653,36 @@ func BenchmarkSortByValue_fnv_1000(b *testing.B) { benchmarkSortByValue(b, 1000, hash) } +func BenchmarkSortHashersByValue_Reflection_fnv_10(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueReflection(b, 10, hash) +} + +func BenchmarkSortHashersByValue_Reflection_fnv_100(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueReflection(b, 100, hash) +} + +func BenchmarkSortHashersByValue_Reflection_fnv_1000(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueReflection(b, 1000, hash) +} + +func BenchmarkSortHashersByValue_Typed_fnv_10(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueTyped(b, 10, hash) +} + +func BenchmarkSortHashersByValue_Typed_fnv_100(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueTyped(b, 100, hash) +} + +func BenchmarkSortHashersByValue_Typed_fnv_1000(b *testing.B) { + hash := Hash(testKey) + benchmarkSortHashersByValueTyped(b, 1000, hash) +} + func BenchmarkSortByWeight_fnv_10(b *testing.B) { hash := Hash(testKey) _ = benchmarkSortByWeight(b, 10, hash) @@ -681,6 +728,30 @@ func BenchmarkSortByWeightValue_fnv_1000(b *testing.B) { benchmarkSortByWeightValue(b, 1000, hash) } +func BenchmarkSortHashersByWeightValueReflection_fnv_10(b *testing.B) { + benchmarkSortHashersByWeightValueRelection(b, 10, Hash(testKey)) +} + +func BenchmarkSortHashersByWeightValueReflection_fnv_100(b *testing.B) { + benchmarkSortHashersByWeightValueRelection(b, 100, Hash(testKey)) +} + +func BenchmarkSortHashersByWeightValueReflection_fnv_1000(b *testing.B) { + benchmarkSortHashersByWeightValueRelection(b, 100, Hash(testKey)) +} + +func BenchmarkSortHashersByWeightValueTyped_fnv_10(b *testing.B) { + benchmarkSortHashersByWeightValueTyped(b, 10, Hash(testKey)) +} + +func BenchmarkSortHashersByWeightValueTyped_fnv_100(b *testing.B) { + benchmarkSortHashersByWeightValueTyped(b, 100, Hash(testKey)) +} + +func BenchmarkSortHashersByWeightValueTyped_fnv_1000(b *testing.B) { + benchmarkSortHashersByWeightValueTyped(b, 100, Hash(testKey)) +} + func benchmarkSort(b *testing.B, n int, hash uint64) uint64 { servers := make([]uint64, n) for i := uint64(0); i < uint64(len(servers)); i++ { @@ -774,3 +845,63 @@ func benchmarkSortByWeightValue(b *testing.B, n int, hash uint64) { SortSliceByWeightValue(servers, weights, hash) } } + +func benchmarkSortHashersByWeightValueRelection(b *testing.B, n int, hash uint64) { + servers := make([]hashString, n) + weights := make([]float64, n) + for i := uint64(0); i < uint64(len(servers)); i++ { + weights[i] = float64(uint64(n)-i) / float64(n) + servers[i] = hashString("localhost:" + strconv.FormatUint(60000-i, 10)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + SortSliceByWeightValue(servers, weights, hash) + } +} + +func benchmarkSortHashersByWeightValueTyped(b *testing.B, n int, hash uint64) { + servers := make([]hashString, n) + weights := make([]float64, n) + for i := uint64(0); i < uint64(len(servers)); i++ { + weights[i] = float64(uint64(n)-i) / float64(n) + servers[i] = hashString("localhost:" + strconv.FormatUint(60000-i, 10)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + SortHasherSliceByWeightValue(servers, weights, hash) + } +} + +func benchmarkSortHashersByValueReflection(b *testing.B, n int, hash uint64) { + servers := make([]hashString, n) + for i := uint64(0); i < uint64(len(servers)); i++ { + servers[i] = hashString("localhost:" + strconv.FormatUint(60000-i, 10)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + SortSliceByValue(servers, hash) + } +} + +func benchmarkSortHashersByValueTyped(b *testing.B, n int, hash uint64) { + servers := make([]hashString, n) + for i := uint64(0); i < uint64(len(servers)); i++ { + servers[i] = hashString("localhost:" + strconv.FormatUint(60000-i, 10)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + SortHasherSliceByValue(servers, hash) + } +}