|
|
@@ -0,0 +1,119 @@
|
|
|
+// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
+// SPDX-License-Identifier: BSD-3-Clause
|
|
|
+
|
|
|
+package syncs
|
|
|
+
|
|
|
+import (
|
|
|
+ "expvar"
|
|
|
+ "sync"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "tailscale.com/tstest"
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ _ expvar.Var = (*ShardedInt)(nil)
|
|
|
+ // TODO(raggi): future go version:
|
|
|
+ // _ encoding.TextAppender = (*ShardedInt)(nil)
|
|
|
+)
|
|
|
+
|
|
|
+func BenchmarkShardedInt(b *testing.B) {
|
|
|
+ b.ReportAllocs()
|
|
|
+
|
|
|
+ b.Run("expvar", func(b *testing.B) {
|
|
|
+ var m expvar.Int
|
|
|
+ b.RunParallel(func(pb *testing.PB) {
|
|
|
+ for pb.Next() {
|
|
|
+ m.Add(1)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ b.Run("sharded int", func(b *testing.B) {
|
|
|
+ m := NewShardedInt()
|
|
|
+ b.RunParallel(func(pb *testing.PB) {
|
|
|
+ for pb.Next() {
|
|
|
+ m.Add(1)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestShardedInt(t *testing.T) {
|
|
|
+ t.Run("basics", func(t *testing.T) {
|
|
|
+ m := NewShardedInt()
|
|
|
+ if got, want := m.Value(), int64(0); got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ m.Add(1)
|
|
|
+ if got, want := m.Value(), int64(1); got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ m.Add(2)
|
|
|
+ if got, want := m.Value(), int64(3); got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ m.Add(-1)
|
|
|
+ if got, want := m.Value(), int64(2); got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("high concurrency", func(t *testing.T) {
|
|
|
+ m := NewShardedInt()
|
|
|
+ wg := sync.WaitGroup{}
|
|
|
+ numWorkers := 1000
|
|
|
+ numIncrements := 1000
|
|
|
+ wg.Add(numWorkers)
|
|
|
+ for i := 0; i < numWorkers; i++ {
|
|
|
+ go func() {
|
|
|
+ defer wg.Done()
|
|
|
+ for i := 0; i < numIncrements; i++ {
|
|
|
+ m.Add(1)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ }
|
|
|
+ wg.Wait()
|
|
|
+ if got, want := m.Value(), int64(numWorkers*numIncrements); got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ for i, shard := range m.GetDistribution() {
|
|
|
+ t.Logf("shard %d: %d", i, shard)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("encoding.TextAppender", func(t *testing.T) {
|
|
|
+ m := NewShardedInt()
|
|
|
+ m.Add(1)
|
|
|
+ b := make([]byte, 0, 10)
|
|
|
+ b, err := m.AppendText(b)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if got, want := string(b), "1"; got != want {
|
|
|
+ t.Errorf("got %v, want %v", got, want)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("allocs", func(t *testing.T) {
|
|
|
+ m := NewShardedInt()
|
|
|
+ tstest.MinAllocsPerRun(t, 0, func() {
|
|
|
+ m.Add(1)
|
|
|
+ _ = m.Value()
|
|
|
+ })
|
|
|
+
|
|
|
+ // TODO(raggi): fix access to expvar's internal append based
|
|
|
+ // interface, unfortunately it's not currently closed for external
|
|
|
+ // use, this will alloc when it escapes.
|
|
|
+ tstest.MinAllocsPerRun(t, 0, func() {
|
|
|
+ m.Add(1)
|
|
|
+ _ = m.String()
|
|
|
+ })
|
|
|
+
|
|
|
+ b := make([]byte, 0, 10)
|
|
|
+ tstest.MinAllocsPerRun(t, 0, func() {
|
|
|
+ m.Add(1)
|
|
|
+ m.AppendText(b)
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|