diff --git a/util/fastuuid/fastuuid.go b/util/fastuuid/fastuuid.go new file mode 100644 index 000000000..4b115ea4e --- /dev/null +++ b/util/fastuuid/fastuuid.go @@ -0,0 +1,56 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Package fastuuid implements a UUID construction using an in process CSPRNG. +package fastuuid + +import ( + crand "crypto/rand" + "encoding/binary" + "io" + "math/rand/v2" + "sync" + + "github.com/google/uuid" +) + +// NewUUID returns a new UUID using a pool of generators, good for highly +// concurrent use. +func NewUUID() uuid.UUID { + g := pool.Get().(*generator) + defer pool.Put(g) + return g.newUUID() +} + +var pool = sync.Pool{ + New: func() any { + return newGenerator() + }, +} + +type generator struct { + rng rand.ChaCha8 +} + +func seed() [32]byte { + var r [32]byte + if _, err := io.ReadFull(crand.Reader, r[:]); err != nil { + panic(err) + } + return r +} + +func newGenerator() *generator { + return &generator{ + rng: *rand.NewChaCha8(seed()), + } +} + +func (g *generator) newUUID() uuid.UUID { + var u uuid.UUID + binary.NativeEndian.PutUint64(u[:8], g.rng.Uint64()) + binary.NativeEndian.PutUint64(u[8:], g.rng.Uint64()) + u[6] = (u[6] & 0x0f) | 0x40 // Version 4 + u[8] = (u[8] & 0x3f) | 0x80 // Variant 10 + return u +} diff --git a/util/fastuuid/fastuuid_test.go b/util/fastuuid/fastuuid_test.go new file mode 100644 index 000000000..b514d6daa --- /dev/null +++ b/util/fastuuid/fastuuid_test.go @@ -0,0 +1,72 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package fastuuid + +import ( + "testing" + + "github.com/google/uuid" +) + +func TestNewUUID(t *testing.T) { + g := pool.Get().(*generator) + defer pool.Put(g) + u := g.newUUID() + if u[6] != (u[6]&0x0f)|0x40 { + t.Errorf("version bits are incorrect") + } + if u[8] != (u[8]&0x3f)|0x80 { + t.Errorf("variant bits are incorrect") + } +} + +func BenchmarkBasic(b *testing.B) { + b.Run("NewUUID", func(b *testing.B) { + for i := 0; i < b.N; i++ { + NewUUID() + } + }) + + b.Run("uuid.New-unpooled", func(b *testing.B) { + uuid.DisableRandPool() + for i := 0; i < b.N; i++ { + uuid.New() + } + }) + + b.Run("uuid.New-pooled", func(b *testing.B) { + uuid.EnableRandPool() + for i := 0; i < b.N; i++ { + uuid.New() + } + }) +} + +func BenchmarkParallel(b *testing.B) { + b.Run("NewUUID", func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + NewUUID() + } + }) + }) + + b.Run("uuid.New-unpooled", func(b *testing.B) { + uuid.DisableRandPool() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + uuid.New() + } + }) + }) + + b.Run("uuid.New-pooled", func(b *testing.B) { + uuid.EnableRandPool() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + uuid.New() + } + }) + }) +}