Skip to content

Instantly share code, notes, and snippets.

@dt
Last active May 19, 2017 18:08
Show Gist options
  • Save dt/1ecb6f067fc452450c576a3255ec07fd to your computer and use it in GitHub Desktop.
Save dt/1ecb6f067fc452450c576a3255ec07fd to your computer and use it in GitHub Desktop.
Computing Incremented UUID
package main
import (
"encoding/binary"
"math/big"
"math/rand"
"reflect"
"testing"
"testing/quick"
)
type uuid [16]byte
func uuidFromInts(hi, lo uint64) uuid {
var u uuid
binary.BigEndian.PutUint64(u[0:8], hi)
binary.BigEndian.PutUint64(u[8:16], lo)
return u
}
func uuidFromBytes(b []byte) uuid {
var u uuid
copy(u[16-len(b):], b)
return u
}
func (u uuid) GetBytes() []byte {
return u[:]
}
func (u uuid) AsInts() (hi, lo uint64) {
return binary.BigEndian.Uint64(u[0:8]), binary.BigEndian.Uint64(u[8:16])
}
// avoid alloc.
var bigOne = big.NewInt(1)
func nextBig(u uuid) uuid {
i := new(big.Int).SetBytes(u.GetBytes())
return uuidFromBytes(i.Add(i, bigOne).Bytes())
}
func prevBig(u uuid) uuid {
i := new(big.Int).SetBytes(u.GetBytes())
return uuidFromBytes(i.Sub(i, bigOne).Bytes())
}
func nextBinary(u uuid) uuid {
hi, lo := u.AsInts()
lo = lo + 1
if lo == 0 {
hi = hi + 1
}
return uuidFromInts(hi, lo)
}
func prevBinary(u uuid) uuid {
hi, lo := u.AsInts()
if lo == 0 {
hi = hi - 1
}
lo = lo - 1
return uuidFromInts(hi, lo)
}
var one = uuid{1}
func BenchmarkNextBig(b *testing.B) {
x := one
for i := 0; i < b.N; i++ {
x = nextBig(x)
}
}
func BenchmarkNextBinary(b *testing.B) {
x := one
for i := 0; i < b.N; i++ {
x = nextBinary(x)
}
}
func BenchmarkPrevBig(b *testing.B) {
x := one
for i := 0; i < b.N; i++ {
x = prevBig(x)
}
}
func BenchmarkPrevBinary(b *testing.B) {
x := one
for i := 0; i < b.N; i++ {
x = prevBinary(x)
}
}
func TestBigMatchesBinary(t *testing.T) {
conf := &quick.Config{
Values: func(args []reflect.Value, r *rand.Rand) {
u := uuid{}
_, _ = rand.Read(u[:])
args[0] = reflect.ValueOf(u)
},
}
t.Run("prevBig(nextBig(x)) == x", func(t *testing.T) {
if err := quick.Check(func(u uuid) bool {
return prevBig(nextBig(u)) == u
}, conf); err != nil {
t.Fatal(err)
}
})
t.Run("nextBig(prevBig(x)) == x", func(t *testing.T) {
if err := quick.Check(func(u uuid) bool {
return nextBinary(prevBinary(u)) == u
}, conf); err != nil {
t.Fatal(err)
}
})
t.Run("next(x) != x", func(t *testing.T) {
if err := quick.Check(func(u uuid) bool {
return nextBinary(u) != u
}, conf); err != nil {
t.Fatal(err)
}
})
t.Run("nextBig == nextBinary", func(t *testing.T) {
if err := quick.CheckEqual(nextBig, nextBinary, conf); err != nil {
t.Error(err)
}
})
t.Run("prevBinary == prevBig", func(t *testing.T) {
if err := quick.CheckEqual(prevBinary, prevBig, conf); err != nil {
t.Error(err)
}
})
}
@dt
Copy link
Author

dt commented May 19, 2017

$ go test -bench . -benchmem
BenchmarkNextBig-4      	 5000000	       270 ns/op	     208 B/op	       5 allocs/op
BenchmarkNextBinary-4   	100000000	       21.9 ns/op	       0 B/op	       0 allocs/op
BenchmarkPrevBig-4      	 5000000	       259 ns/op	     192 B/op	       5 allocs/op
BenchmarkPrevBinary-4   	100000000	       22.2 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/cockroachdb/cockroach/meh	7.663s

@dt
Copy link
Author

dt commented May 19, 2017

pulling the big.NewInt(1) into a re-used var and reusing i in the Add and Sub calls sheds 3 of the 5 allocs in the big methods, but it is still not even close:

go test -bench . -benchmem
BenchmarkNextBig-4      	10000000	       159 ns/op	      64 B/op	       2 allocs/op
BenchmarkNextBinary-4   	100000000	        22.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkPrevBig-4      	10000000	       156 ns/op	      64 B/op	       2 allocs/op
BenchmarkPrevBinary-4   	100000000	        22.4 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/cockroachdb/cockroach/meh	7.996s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment