shardedint_test.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package syncs_test
  4. import (
  5. "expvar"
  6. "sync"
  7. "testing"
  8. . "tailscale.com/syncs"
  9. "tailscale.com/tstest"
  10. )
  11. var (
  12. _ expvar.Var = (*ShardedInt)(nil)
  13. // TODO(raggi): future go version:
  14. // _ encoding.TextAppender = (*ShardedInt)(nil)
  15. )
  16. func BenchmarkShardedInt(b *testing.B) {
  17. b.ReportAllocs()
  18. b.Run("expvar", func(b *testing.B) {
  19. var m expvar.Int
  20. b.RunParallel(func(pb *testing.PB) {
  21. for pb.Next() {
  22. m.Add(1)
  23. }
  24. })
  25. })
  26. b.Run("sharded int", func(b *testing.B) {
  27. m := NewShardedInt()
  28. b.RunParallel(func(pb *testing.PB) {
  29. for pb.Next() {
  30. m.Add(1)
  31. }
  32. })
  33. })
  34. }
  35. func TestShardedInt(t *testing.T) {
  36. t.Run("basics", func(t *testing.T) {
  37. m := NewShardedInt()
  38. if got, want := m.Value(), int64(0); got != want {
  39. t.Errorf("got %v, want %v", got, want)
  40. }
  41. m.Add(1)
  42. if got, want := m.Value(), int64(1); got != want {
  43. t.Errorf("got %v, want %v", got, want)
  44. }
  45. m.Add(2)
  46. if got, want := m.Value(), int64(3); got != want {
  47. t.Errorf("got %v, want %v", got, want)
  48. }
  49. m.Add(-1)
  50. if got, want := m.Value(), int64(2); got != want {
  51. t.Errorf("got %v, want %v", got, want)
  52. }
  53. })
  54. t.Run("high concurrency", func(t *testing.T) {
  55. m := NewShardedInt()
  56. wg := sync.WaitGroup{}
  57. numWorkers := 1000
  58. numIncrements := 1000
  59. wg.Add(numWorkers)
  60. for i := 0; i < numWorkers; i++ {
  61. go func() {
  62. defer wg.Done()
  63. for i := 0; i < numIncrements; i++ {
  64. m.Add(1)
  65. }
  66. }()
  67. }
  68. wg.Wait()
  69. if got, want := m.Value(), int64(numWorkers*numIncrements); got != want {
  70. t.Errorf("got %v, want %v", got, want)
  71. }
  72. for i, shard := range m.GetDistribution() {
  73. t.Logf("shard %d: %d", i, shard)
  74. }
  75. })
  76. t.Run("encoding.TextAppender", func(t *testing.T) {
  77. m := NewShardedInt()
  78. m.Add(1)
  79. b := make([]byte, 0, 10)
  80. b, err := m.AppendText(b)
  81. if err != nil {
  82. t.Fatal(err)
  83. }
  84. if got, want := string(b), "1"; got != want {
  85. t.Errorf("got %v, want %v", got, want)
  86. }
  87. })
  88. t.Run("allocs", func(t *testing.T) {
  89. m := NewShardedInt()
  90. tstest.MinAllocsPerRun(t, 0, func() {
  91. m.Add(1)
  92. _ = m.Value()
  93. })
  94. // TODO(raggi): fix access to expvar's internal append based
  95. // interface, unfortunately it's not currently closed for external
  96. // use, this will alloc when it escapes.
  97. tstest.MinAllocsPerRun(t, 0, func() {
  98. m.Add(1)
  99. _ = m.String()
  100. })
  101. b := make([]byte, 0, 10)
  102. tstest.MinAllocsPerRun(t, 0, func() {
  103. m.Add(1)
  104. m.AppendText(b)
  105. })
  106. })
  107. }