|
| 1 | +package broadcast_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "sync" |
| 6 | + "testing" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/NethermindEth/juno/broadcaster/broadcast" |
| 10 | +) |
| 11 | + |
| 12 | +// benchmarkThroughput measures one configuration of Broadcast end to end: nPubs publishers |
| 13 | +// into one ring, nSubs subscriptions draining channels. b.N sends are split across the |
| 14 | +// publishers. Callers vary the parameters. |
| 15 | +func benchmarkThroughput[T any]( |
| 16 | + b *testing.B, |
| 17 | + payload T, |
| 18 | + label string, |
| 19 | + bufSize uint64, |
| 20 | + nPubs, |
| 21 | + nSubs int, |
| 22 | +) { |
| 23 | + name := fmt.Sprintf("%s_buffer=%d/pubs=%d/subs=%d", label, bufSize, nPubs, nSubs) |
| 24 | + b.Run(name, func(b *testing.B) { |
| 25 | + bc := broadcast.New[T](bufSize) |
| 26 | + subbable := bc.NewSubscribable() |
| 27 | + |
| 28 | + type counts struct { |
| 29 | + recvd uint64 |
| 30 | + lag uint64 |
| 31 | + } |
| 32 | + countCh := make(chan counts, nSubs) |
| 33 | + |
| 34 | + unsubs := make([]func(), nSubs) |
| 35 | + var readers sync.WaitGroup |
| 36 | + for i := range nSubs { |
| 37 | + sub := subbable.Subscribe() |
| 38 | + unsubs[i] = sub.Unsubscribe |
| 39 | + readers.Go(func() { |
| 40 | + var c counts |
| 41 | + for ev := range sub.Recv() { |
| 42 | + if _, ok := ev.AsEvent(); ok { |
| 43 | + c.recvd++ |
| 44 | + } else { |
| 45 | + c.lag++ |
| 46 | + } |
| 47 | + } |
| 48 | + countCh <- c |
| 49 | + }) |
| 50 | + } |
| 51 | + |
| 52 | + // b.Loop cannot be used here: the iteration count has to be divided among the |
| 53 | + // publishers before any of them starts. |
| 54 | + perPub := b.N / nPubs |
| 55 | + var writers sync.WaitGroup |
| 56 | + b.ResetTimer() |
| 57 | + start := time.Now() |
| 58 | + for range nPubs { |
| 59 | + pub := bc.NewPublisher() |
| 60 | + writers.Go(func() { |
| 61 | + for range perPub { |
| 62 | + pub.Send(payload) |
| 63 | + } |
| 64 | + }) |
| 65 | + } |
| 66 | + writers.Wait() |
| 67 | + b.StopTimer() |
| 68 | + duration := time.Since(start).Seconds() |
| 69 | + |
| 70 | + for _, unsub := range unsubs { |
| 71 | + unsub() |
| 72 | + } |
| 73 | + readers.Wait() |
| 74 | + close(countCh) |
| 75 | + |
| 76 | + var totalRecvd, totalLag uint64 |
| 77 | + for c := range countCh { |
| 78 | + totalRecvd += c.recvd |
| 79 | + totalLag += c.lag |
| 80 | + } |
| 81 | + |
| 82 | + sent := float64(perPub * nPubs) |
| 83 | + b.ReportMetric(sent/duration, "msgs_sent_per_sec") |
| 84 | + b.ReportMetric(float64(totalRecvd)/duration, "msgs_recv_per_sec") |
| 85 | + b.ReportMetric(float64(totalRecvd)/float64(nSubs), "avg_msgs_recv_per_sub") |
| 86 | + b.ReportMetric(float64(totalLag)/float64(nSubs), "avg_lag_per_sub") |
| 87 | + b.ReportMetric(float64(totalRecvd)/(sent*float64(nSubs)), "delivered_fraction") |
| 88 | + }) |
| 89 | +} |
| 90 | + |
| 91 | +// BenchmarkBroadcastPublisherThroughput varies ring capacity and subscriber count for a |
| 92 | +// single publisher, across four payload shapes. |
| 93 | +func BenchmarkBroadcastPublisherThroughput(b *testing.B) { |
| 94 | + type BigStruct struct { |
| 95 | + Field1 [128]byte |
| 96 | + Field2 [256]int64 |
| 97 | + Field3 string |
| 98 | + Field4 float64 |
| 99 | + } |
| 100 | + payload := 10 |
| 101 | + payloadBig := BigStruct{Field3: "benchmark big struct payload"} |
| 102 | + |
| 103 | + for _, bufSize := range []uint64{64, 256, 512, 1024, 2048, 4096} { |
| 104 | + for _, nSubs := range []int{1, 4, 32, 128, 256, 512, 1024} { |
| 105 | + benchmarkThroughput(b, payload, "int_value", bufSize, 1, nSubs) |
| 106 | + benchmarkThroughput(b, &payload, "int_ptr", bufSize, 1, nSubs) |
| 107 | + benchmarkThroughput(b, payloadBig, "big_struct_value", bufSize, 1, nSubs) |
| 108 | + benchmarkThroughput(b, &payloadBig, "big_struct_ptr", bufSize, 1, nSubs) |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +// BenchmarkBroadcastMultiPublisherThroughput holds capacity and payload fixed and varies |
| 114 | +// publisher and subscriber counts instead. |
| 115 | +func BenchmarkBroadcastMultiPublisherThroughput(b *testing.B) { |
| 116 | + payload := 10 |
| 117 | + |
| 118 | + for _, nPubs := range []int{1, 2, 4, 8} { |
| 119 | + for _, nSubs := range []int{1, 32, 256, 1024} { |
| 120 | + benchmarkThroughput(b, &payload, "int_ptr", 1024, nPubs, nSubs) |
| 121 | + } |
| 122 | + } |
| 123 | +} |
0 commit comments