hd: optimize ReverseBytes + add tests

* Optimized ReverseBytes to:
a) Minimally allocate --> 60.0% reduction in the number of allocations
b) Only walk halfway the length of the string thus performing
byte swaps from left to right. Improves the performance as well.
Complexity is O(n/2) instead of O(n) which is still O(n) but
benchmarks show the new time is in deed 1/2 of the original time.

* Added unit tests and some common cases to ensure correctness.

* Benchmark shoot out results:
```shell
name            old time/op    new time/op    delta
ReverseBytes-4     554ns ± 4%     242ns ± 3%  -56.20%  (p=0.000 n=10+10)

name            old alloc/op   new alloc/op   delta
ReverseBytes-4      208B ± 0%      114B ± 0%  -45.19%  (p=0.000 n=10+10)

name            old allocs/op  new allocs/op  delta
ReverseBytes-4      10.0 ± 0%       4.0 ± 0%  -60.00%  (p=0.000 n=10+10)
```
This commit is contained in:
Emmanuel Odeke 2017-07-26 20:43:27 -06:00
parent bf355d1b58
commit ae9c5b1ca0
2 changed files with 67 additions and 3 deletions

View File

@ -282,9 +282,20 @@ func CalcSha512(buf []byte) []byte {
}
func ReverseBytes(buf []byte) []byte {
res := []byte{}
for i := len(buf) - 1; i >= 0; i-- {
res = append(res, buf[i])
var res []byte
if len(buf) == 0 {
return res
}
// Walk till mid-way, swapping bytes from each end:
// b[i] and b[len-i-1]
blen := len(buf)
res = make([]byte, blen)
mid := blen / 2
for left := 0; left <= mid; left++ {
right := blen - left - 1
res[left] = buf[right]
res[right] = buf[left]
}
return res
}

View File

@ -89,6 +89,26 @@ func TestHDToAddr(t *testing.T) {
}
}
func TestReverseBytes(t *testing.T) {
tests := [...]struct {
v []byte
want []byte
}{
{[]byte(""), []byte("")},
{nil, nil},
{[]byte("Tendermint"), []byte("tnimredneT")},
{[]byte("T"), []byte("T")},
{[]byte("Te"), []byte("eT")},
}
for i, tt := range tests {
got := ReverseBytes(tt.v)
if !bytes.Equal(got, tt.want) {
t.Errorf("#%d:\ngot= (%x)\nwant=(%x)", i, got, tt.want)
}
}
}
func ifExit(err error, n int) {
if err != nil {
fmt.Println(n, err)
@ -187,3 +207,36 @@ func tylerSmith(seed []byte) ([]byte, []byte, []byte) {
pub := k.PublicKey().Key
return masterKey.Key, priv, pub
}
// Benchmarks
var revBytesCases = [][]byte{
nil,
[]byte(""),
[]byte("12"),
// 16byte case
[]byte("abcdefghijklmnop"),
// 32byte case
[]byte("abcdefghijklmnopqrstuvwxyz123456"),
// 64byte case
[]byte("abcdefghijklmnopqrstuvwxyz123456abcdefghijklmnopqrstuvwxyz123456"),
}
func BenchmarkReverseBytes(b *testing.B) {
var sink []byte
for i := 0; i < b.N; i++ {
for _, tt := range revBytesCases {
sink = ReverseBytes(tt)
}
}
b.ReportAllocs()
// sink is necessary to ensure if the compiler tries
// to smart, that it won't optimize away the benchmarks.
if sink != nil {
}
}