Skip to content

Commit 9e07992

Browse files
[Delegations prereq] Add hash bins helpers
Splitting up #175
1 parent 5dad9b6 commit 9e07992

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed

internal/targets/hash_bins.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package targets
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
)
7+
8+
// hexEncode formats x as a hex string, left padded with zeros to padWidth.
9+
func hexEncode(x uint64, padWidth int) string {
10+
// Benchmarked to be more than 10x faster than padding with Sprintf.
11+
s := strconv.FormatUint(x, 16)
12+
if len(s) >= padWidth {
13+
return s
14+
}
15+
return strings.Repeat("0", padWidth-len(s)) + s
16+
}
17+
18+
// HashBin represents a hex prefix range. First should be less than Last.
19+
type HashBin struct {
20+
First uint64
21+
Last uint64
22+
}
23+
24+
// Name returns the of the role that signs for the HashBin.
25+
func (b HashBin) Name(prefix string, padWidth int) string {
26+
if b.First == b.Last {
27+
return prefix + hexEncode(b.First, padWidth)
28+
}
29+
30+
return prefix + hexEncode(b.First, padWidth) + "-" + hexEncode(b.Last, padWidth)
31+
}
32+
33+
// Enumerate returns a slice of hash prefixes in the range from First to Last.
34+
func (b HashBin) Enumerate(padWidth int) []string {
35+
n := int(b.Last - b.First + 1)
36+
ret := make([]string, int(n))
37+
38+
x := b.First
39+
for i := 0; i < n; i++ {
40+
ret[i] = hexEncode(x, padWidth)
41+
x++
42+
}
43+
44+
return ret
45+
}
46+
47+
// HashPrefixLength returns the width of hash prefixes if there are
48+
// 2^(log2NumBins) hash bins.
49+
func HashPrefixLength(log2NumBins uint8) int {
50+
if log2NumBins == 0 {
51+
// Hash prefix of "" is represented equivalently as "0-f".
52+
return 1
53+
}
54+
55+
// ceil(log2NumBins / 4.0)
56+
return int((log2NumBins-1)/4) + 1
57+
}
58+
59+
// GenerateHashBins returns a slice of length 2^(log2NumBins) that partitions
60+
// the space of path hashes into HashBin ranges.
61+
func GenerateHashBins(log2NumBins uint8) []HashBin {
62+
numBins := uint64(1) << log2NumBins
63+
64+
// numPrefixes = 16^(HashPrefixLength(log2NumBins))
65+
numPrefixes := uint64(1) << (4 * HashPrefixLength(log2NumBins))
66+
67+
p := make([]HashBin, numBins)
68+
69+
first := uint64(0)
70+
interval := numPrefixes / numBins
71+
last := first + interval - 1
72+
for i := uint64(0); i < numBins; i++ {
73+
p[i] = HashBin{
74+
First: first,
75+
Last: last,
76+
}
77+
first += interval
78+
last += interval
79+
}
80+
81+
return p
82+
}

internal/targets/hash_bins_test.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package targets
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func BenchmarkHexEncode1(b *testing.B) {
10+
for n := 0; n <= b.N; n++ {
11+
for x := uint64(0); x <= 0xf; x += 1 {
12+
hexEncode(x, 1)
13+
}
14+
}
15+
}
16+
17+
func BenchmarkHexEncode4(b *testing.B) {
18+
for n := 0; n <= b.N; n++ {
19+
for x := uint64(0); x <= 0xffff; x += 1 {
20+
hexEncode(x, 4)
21+
}
22+
}
23+
}
24+
25+
func TestHashBin(t *testing.T) {
26+
h := HashBin{
27+
First: 0x0,
28+
Last: 0xf,
29+
}
30+
assert.Equal(t, "abc_0-f", h.Name("abc_", 1))
31+
assert.Equal(t, "abc_0000-000f", h.Name("abc_", 4))
32+
assert.Equal(t, []string{
33+
"00", "01", "02", "03", "04", "05", "06", "07",
34+
"08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
35+
}, h.Enumerate(2))
36+
37+
h = HashBin{
38+
First: 0xcd,
39+
Last: 0xce,
40+
}
41+
assert.Equal(t, "abc_00cd-00ce", h.Name("abc_", 4))
42+
assert.Equal(t, []string{"00cd", "00ce"}, h.Enumerate(4))
43+
44+
h = HashBin{
45+
First: 0x0abc,
46+
Last: 0xbcde,
47+
}
48+
assert.Equal(t, "test_0abc-bcde", h.Name("test_", 4))
49+
}
50+
51+
func TestHashPrefixLength(t *testing.T) {
52+
assert.Equal(t, 1, HashPrefixLength(0))
53+
assert.Equal(t, 1, HashPrefixLength(1))
54+
assert.Equal(t, 1, HashPrefixLength(2))
55+
assert.Equal(t, 1, HashPrefixLength(3))
56+
assert.Equal(t, 1, HashPrefixLength(4))
57+
assert.Equal(t, 2, HashPrefixLength(5))
58+
assert.Equal(t, 2, HashPrefixLength(6))
59+
assert.Equal(t, 2, HashPrefixLength(7))
60+
assert.Equal(t, 2, HashPrefixLength(8))
61+
assert.Equal(t, 3, HashPrefixLength(9))
62+
assert.Equal(t, 3, HashPrefixLength(10))
63+
assert.Equal(t, 3, HashPrefixLength(11))
64+
assert.Equal(t, 3, HashPrefixLength(12))
65+
}
66+
67+
func TestGenerateHashBins(t *testing.T) {
68+
tcs := []struct {
69+
Log2NumBins uint8
70+
BinNames []string
71+
}{
72+
{0, []string{"0-f"}},
73+
{1, []string{"0-7", "8-f"}},
74+
{2, []string{"0-3", "4-7", "8-b", "c-f"}},
75+
{3, []string{"0-1", "2-3", "4-5", "6-7", "8-9", "a-b", "c-d", "e-f"}},
76+
{4, []string{
77+
"0", "1", "2", "3", "4", "5", "6", "7",
78+
"8", "9", "a", "b", "c", "d", "e", "f",
79+
}},
80+
{5, []string{
81+
"00-07", "08-0f", "10-17", "18-1f", "20-27", "28-2f", "30-37", "38-3f",
82+
"40-47", "48-4f", "50-57", "58-5f", "60-67", "68-6f", "70-77", "78-7f",
83+
"80-87", "88-8f", "90-97", "98-9f", "a0-a7", "a8-af", "b0-b7", "b8-bf",
84+
"c0-c7", "c8-cf", "d0-d7", "d8-df", "e0-e7", "e8-ef", "f0-f7", "f8-ff",
85+
}},
86+
}
87+
for _, tc := range tcs {
88+
bn := []string{}
89+
bins := GenerateHashBins(tc.Log2NumBins)
90+
for _, b := range bins {
91+
bn = append(bn, b.Name("", HashPrefixLength(tc.Log2NumBins)))
92+
}
93+
assert.Equal(t, tc.BinNames, bn, "GenerateHashBins(%v)", tc.Log2NumBins)
94+
}
95+
}

0 commit comments

Comments
 (0)