Skip to content

Commit

Permalink
[Delegations prereq] Add hash bins helpers
Browse files Browse the repository at this point in the history
Splitting up #175
  • Loading branch information
ethan-lowman-dd committed Dec 10, 2021
1 parent 5dad9b6 commit 9e07992
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
82 changes: 82 additions & 0 deletions internal/targets/hash_bins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package targets

import (
"strconv"
"strings"
)

// hexEncode formats x as a hex string, left padded with zeros to padWidth.
func hexEncode(x uint64, padWidth int) string {
// Benchmarked to be more than 10x faster than padding with Sprintf.
s := strconv.FormatUint(x, 16)
if len(s) >= padWidth {
return s
}
return strings.Repeat("0", padWidth-len(s)) + s
}

// HashBin represents a hex prefix range. First should be less than Last.
type HashBin struct {
First uint64
Last uint64
}

// Name returns the of the role that signs for the HashBin.
func (b HashBin) Name(prefix string, padWidth int) string {
if b.First == b.Last {
return prefix + hexEncode(b.First, padWidth)
}

return prefix + hexEncode(b.First, padWidth) + "-" + hexEncode(b.Last, padWidth)
}

// Enumerate returns a slice of hash prefixes in the range from First to Last.
func (b HashBin) Enumerate(padWidth int) []string {
n := int(b.Last - b.First + 1)
ret := make([]string, int(n))

x := b.First
for i := 0; i < n; i++ {
ret[i] = hexEncode(x, padWidth)
x++
}

return ret
}

// HashPrefixLength returns the width of hash prefixes if there are
// 2^(log2NumBins) hash bins.
func HashPrefixLength(log2NumBins uint8) int {
if log2NumBins == 0 {
// Hash prefix of "" is represented equivalently as "0-f".
return 1
}

// ceil(log2NumBins / 4.0)
return int((log2NumBins-1)/4) + 1
}

// GenerateHashBins returns a slice of length 2^(log2NumBins) that partitions
// the space of path hashes into HashBin ranges.
func GenerateHashBins(log2NumBins uint8) []HashBin {
numBins := uint64(1) << log2NumBins

// numPrefixes = 16^(HashPrefixLength(log2NumBins))
numPrefixes := uint64(1) << (4 * HashPrefixLength(log2NumBins))

p := make([]HashBin, numBins)

first := uint64(0)
interval := numPrefixes / numBins
last := first + interval - 1
for i := uint64(0); i < numBins; i++ {
p[i] = HashBin{
First: first,
Last: last,
}
first += interval
last += interval
}

return p
}
95 changes: 95 additions & 0 deletions internal/targets/hash_bins_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package targets

import (
"testing"

"github.com/stretchr/testify/assert"
)

func BenchmarkHexEncode1(b *testing.B) {
for n := 0; n <= b.N; n++ {
for x := uint64(0); x <= 0xf; x += 1 {
hexEncode(x, 1)
}
}
}

func BenchmarkHexEncode4(b *testing.B) {
for n := 0; n <= b.N; n++ {
for x := uint64(0); x <= 0xffff; x += 1 {
hexEncode(x, 4)
}
}
}

func TestHashBin(t *testing.T) {
h := HashBin{
First: 0x0,
Last: 0xf,
}
assert.Equal(t, "abc_0-f", h.Name("abc_", 1))
assert.Equal(t, "abc_0000-000f", h.Name("abc_", 4))
assert.Equal(t, []string{
"00", "01", "02", "03", "04", "05", "06", "07",
"08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
}, h.Enumerate(2))

h = HashBin{
First: 0xcd,
Last: 0xce,
}
assert.Equal(t, "abc_00cd-00ce", h.Name("abc_", 4))
assert.Equal(t, []string{"00cd", "00ce"}, h.Enumerate(4))

h = HashBin{
First: 0x0abc,
Last: 0xbcde,
}
assert.Equal(t, "test_0abc-bcde", h.Name("test_", 4))
}

func TestHashPrefixLength(t *testing.T) {
assert.Equal(t, 1, HashPrefixLength(0))
assert.Equal(t, 1, HashPrefixLength(1))
assert.Equal(t, 1, HashPrefixLength(2))
assert.Equal(t, 1, HashPrefixLength(3))
assert.Equal(t, 1, HashPrefixLength(4))
assert.Equal(t, 2, HashPrefixLength(5))
assert.Equal(t, 2, HashPrefixLength(6))
assert.Equal(t, 2, HashPrefixLength(7))
assert.Equal(t, 2, HashPrefixLength(8))
assert.Equal(t, 3, HashPrefixLength(9))
assert.Equal(t, 3, HashPrefixLength(10))
assert.Equal(t, 3, HashPrefixLength(11))
assert.Equal(t, 3, HashPrefixLength(12))
}

func TestGenerateHashBins(t *testing.T) {
tcs := []struct {
Log2NumBins uint8
BinNames []string
}{
{0, []string{"0-f"}},
{1, []string{"0-7", "8-f"}},
{2, []string{"0-3", "4-7", "8-b", "c-f"}},
{3, []string{"0-1", "2-3", "4-5", "6-7", "8-9", "a-b", "c-d", "e-f"}},
{4, []string{
"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "a", "b", "c", "d", "e", "f",
}},
{5, []string{
"00-07", "08-0f", "10-17", "18-1f", "20-27", "28-2f", "30-37", "38-3f",
"40-47", "48-4f", "50-57", "58-5f", "60-67", "68-6f", "70-77", "78-7f",
"80-87", "88-8f", "90-97", "98-9f", "a0-a7", "a8-af", "b0-b7", "b8-bf",
"c0-c7", "c8-cf", "d0-d7", "d8-df", "e0-e7", "e8-ef", "f0-f7", "f8-ff",
}},
}
for _, tc := range tcs {
bn := []string{}
bins := GenerateHashBins(tc.Log2NumBins)
for _, b := range bins {
bn = append(bn, b.Name("", HashPrefixLength(tc.Log2NumBins)))
}
assert.Equal(t, tc.BinNames, bn, "GenerateHashBins(%v)", tc.Log2NumBins)
}
}

0 comments on commit 9e07992

Please sign in to comment.