From 895ecf150f5367ebd8c687f69a82f3a1666ca9d0 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 1 Jun 2023 20:22:47 +0300 Subject: [PATCH] [#8] hrw: Inline swap() when slice is known MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/hrw cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ 2 │ 3 │ │ sec/op │ sec/op vs base │ SortHashersByValue_Typed_fnv_10-8 368.5n ± 2% 336.3n ± 3% -8.75% (p=0.000 n=10) SortHashersByValue_Typed_fnv_100-8 2.411µ ± 4% 2.424µ ± 3% ~ (p=0.853 n=10) SortHashersByValue_Typed_fnv_1000-8 22.19µ ± 2% 22.35µ ± 1% ~ (p=0.247 n=10) SortHashersByWeightValueTyped_fnv_10-8 364.3n ± 2% 346.6n ± 3% -4.86% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_100-8 2.541µ ± 3% 2.637µ ± 6% ~ (p=0.055 n=10) SortHashersByWeightValueTyped_fnv_1000-8 2.483µ ± 1% 2.609µ ± 4% +5.07% (p=0.003 n=10) geomean 1.888µ 1.875µ -0.71% │ 2 │ 3 │ │ B/op │ B/op vs base │ SortHashersByValue_Typed_fnv_10-8 312.0 ± 0% 296.0 ± 0% -5.13% (p=0.000 n=10) SortHashersByValue_Typed_fnv_100-8 1.898Ki ± 0% 1.883Ki ± 0% -0.82% (p=0.000 n=10) SortHashersByValue_Typed_fnv_1000-8 16.15Ki ± 0% 16.13Ki ± 0% -0.10% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_10-8 312.0 ± 0% 296.0 ± 0% -5.13% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_100-8 1.898Ki ± 0% 1.883Ki ± 0% -0.82% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_1000-8 1.898Ki ± 0% 1.883Ki ± 0% -0.82% (p=0.000 n=10) geomean 1.474Ki 1.442Ki -2.16% │ 2 │ 3 │ │ allocs/op │ allocs/op vs base │ SortHashersByValue_Typed_fnv_10-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) SortHashersByValue_Typed_fnv_100-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) SortHashersByValue_Typed_fnv_1000-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_10-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_100-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) SortHashersByWeightValueTyped_fnv_1000-8 6.000 ± 0% 5.000 ± 0% -16.67% (p=0.000 n=10) geomean 6.000 5.000 -16.67% ``` Signed-off-by: Evgenii Stratonikov --- hrw.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/hrw.go b/hrw.go index c9a7eeb..2effa3a 100644 --- a/hrw.go +++ b/hrw.go @@ -87,10 +87,7 @@ func SortSliceByValue(slice interface{}, hash uint64) { 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) + sortHasherByDistance(slice, false, rule, hash) } } @@ -107,13 +104,59 @@ func SortSliceByWeightValue(slice interface{}, weights []float64, hash uint64) { 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) + sortHasherByWeight(slice, false, rule, weights, hash) } } +// sortHasherByDistance is similar to sortByDistance but accepts slice directly. +func sortHasherByDistance[T Hasher](slice []T, byIndex bool, nodes []uint64, hash uint64) { + dist := make([]uint64, len(slice)) + for i := range dist { + dist[i] = getDistance(byIndex, i, nodes, hash) + } + + s := &sorter{ + l: len(slice), + swap: func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + dist[i], dist[j] = dist[j], dist[i] + }, + less: func(i, j int) bool { + return dist[i] < dist[j] + }, + } + sort.Sort(s) +} + +// sortHasherByWeight is similar to sortByWeight but accepts slice directly. +func sortHasherByWeight[T Hasher](slice []T, byIndex bool, nodes []uint64, weights []float64, hash uint64) { + // if all nodes have the same distance then sort uniformly + if allSameF64(weights) { + sortHasherByDistance(slice, byIndex, nodes, hash) + return + } + + dist := make([]float64, len(slice)) + for i := range dist { + d := getDistance(byIndex, i, nodes, hash) + // `maxUint64 - distance` makes the shorter distance more valuable + // it is necessary for operation with normalized values + dist[i] = float64(^uint64(0)-d) * weights[i] + } + + s := &sorter{ + l: len(slice), + swap: func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + dist[i], dist[j] = dist[j], dist[i] + }, + less: func(i, j int) bool { + return dist[i] > dist[j] // higher distance must be placed lower to be first + }, + } + sort.Sort(s) +} + // SortSliceByIndex received []T and hash to sort by index-distance func SortSliceByIndex(slice interface{}, hash uint64) { length := reflect.ValueOf(slice).Len()