Source file src/net/http/internal/http2/writesched_benchmarks_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http2
     6  
     7  import (
     8  	"testing"
     9  )
    10  
    11  func benchmarkThroughput(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) {
    12  	const maxFrameSize = 16
    13  	const streamCount = 100
    14  
    15  	ws := wsFunc()
    16  	sc := &serverConn{maxFrameSize: maxFrameSize}
    17  	streams := make([]*stream, streamCount)
    18  	// Possible stream payloads. We vary the payload size of different streams
    19  	// to simulate real traffic somewhat.
    20  	streamsFrame := [][]byte{
    21  		make([]byte, maxFrameSize*5),
    22  		make([]byte, maxFrameSize*10),
    23  		make([]byte, maxFrameSize*15),
    24  		make([]byte, maxFrameSize*20),
    25  		make([]byte, maxFrameSize*25),
    26  	}
    27  	for i := range streams {
    28  		streamID := uint32(i) + 1
    29  		streams[i] = &stream{
    30  			id: streamID,
    31  			sc: sc,
    32  		}
    33  		streams[i].flow.add(1 << 30) // arbitrary large value
    34  
    35  		ws.OpenStream(streamID, OpenStreamOptions{
    36  			priority: priority,
    37  		})
    38  	}
    39  
    40  	for b.Loop() {
    41  		for i := range streams {
    42  			streamID := uint32(i) + 1
    43  			ws.Push(FrameWriteRequest{
    44  				write: &writeData{
    45  					streamID:  streamID,
    46  					p:         streamsFrame[i%len(streamsFrame)],
    47  					endStream: false,
    48  				},
    49  				stream: streams[i],
    50  			})
    51  		}
    52  		for {
    53  			wr, ok := ws.Pop()
    54  			if !ok {
    55  				break
    56  			}
    57  			if wr.DataSize() != maxFrameSize {
    58  				b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
    59  			}
    60  		}
    61  	}
    62  
    63  	for i := range streams {
    64  		streamID := uint32(i) + 1
    65  		ws.CloseStream(streamID)
    66  	}
    67  }
    68  
    69  func benchmarkStreamLifetime(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) {
    70  	const maxFrameSize = 16
    71  	const streamCount = 100
    72  
    73  	ws := wsFunc()
    74  	sc := &serverConn{maxFrameSize: maxFrameSize}
    75  	streams := make([]*stream, streamCount)
    76  	// Possible stream payloads. We vary the payload size of different streams
    77  	// to simulate real traffic somewhat.
    78  	streamsFrame := [][]byte{
    79  		make([]byte, maxFrameSize*5),
    80  		make([]byte, maxFrameSize*10),
    81  		make([]byte, maxFrameSize*15),
    82  		make([]byte, maxFrameSize*20),
    83  		make([]byte, maxFrameSize*25),
    84  	}
    85  	for i := range streams {
    86  		streamID := uint32(i) + 1
    87  		streams[i] = &stream{
    88  			id: streamID,
    89  			sc: sc,
    90  		}
    91  		streams[i].flow.add(1 << 30) // arbitrary large value
    92  	}
    93  
    94  	for b.Loop() {
    95  		for i := range streams {
    96  			streamID := uint32(i) + 1
    97  			ws.OpenStream(streamID, OpenStreamOptions{
    98  				priority: priority,
    99  			})
   100  			ws.Push(FrameWriteRequest{
   101  				write: &writeData{
   102  					streamID:  streamID,
   103  					p:         streamsFrame[i%len(streamsFrame)],
   104  					endStream: false,
   105  				},
   106  				stream: streams[i],
   107  			})
   108  		}
   109  		for {
   110  			wr, ok := ws.Pop()
   111  			if !ok {
   112  				break
   113  			}
   114  			if wr.DataSize() != maxFrameSize {
   115  				b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
   116  			}
   117  		}
   118  		for i := range streams {
   119  			streamID := uint32(i) + 1
   120  			ws.CloseStream(streamID)
   121  		}
   122  	}
   123  
   124  }
   125  
   126  func BenchmarkWriteSchedulerThroughputRoundRobin(b *testing.B) {
   127  	benchmarkThroughput(b, newRoundRobinWriteScheduler, PriorityParam{})
   128  }
   129  
   130  func BenchmarkWriteSchedulerLifetimeRoundRobin(b *testing.B) {
   131  	benchmarkStreamLifetime(b, newRoundRobinWriteScheduler, PriorityParam{})
   132  }
   133  
   134  func BenchmarkWriteSchedulerThroughputRandom(b *testing.B) {
   135  	benchmarkThroughput(b, NewRandomWriteScheduler, PriorityParam{})
   136  }
   137  
   138  func BenchmarkWriteSchedulerLifetimeRandom(b *testing.B) {
   139  	benchmarkStreamLifetime(b, NewRandomWriteScheduler, PriorityParam{})
   140  }
   141  
   142  func BenchmarkWriteSchedulerThroughputPriorityRFC7540(b *testing.B) {
   143  	benchmarkThroughput(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{})
   144  }
   145  
   146  func BenchmarkWriteSchedulerLifetimePriorityRFC7540(b *testing.B) {
   147  	// RFC7540 priority scheduler does not always succeed in closing the
   148  	// stream, causing this benchmark to panic due to opening an already open
   149  	// stream.
   150  	b.SkipNow()
   151  	benchmarkStreamLifetime(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{})
   152  }
   153  
   154  func BenchmarkWriteSchedulerThroughputPriorityRFC9218Incremental(b *testing.B) {
   155  	benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
   156  		incremental: 1,
   157  	})
   158  }
   159  
   160  func BenchmarkWriteSchedulerLifetimePriorityRFC9218Incremental(b *testing.B) {
   161  	benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
   162  		incremental: 1,
   163  	})
   164  }
   165  
   166  func BenchmarkWriteSchedulerThroughputPriorityRFC9218NonIncremental(b *testing.B) {
   167  	benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
   168  		incremental: 0,
   169  	})
   170  }
   171  
   172  func BenchmarkWriteSchedulerLifetimePriorityRFC9218NonIncremental(b *testing.B) {
   173  	benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
   174  		incremental: 0,
   175  	})
   176  }
   177  
   178  func BenchmarkWriteQueue(b *testing.B) {
   179  	var qp writeQueuePool
   180  	frameCount := 25
   181  	for b.Loop() {
   182  		q := qp.get()
   183  		for range frameCount {
   184  			q.push(FrameWriteRequest{})
   185  		}
   186  		for !q.empty() {
   187  			// Since we pushed empty frames, consuming 1 byte is enough to
   188  			// consume the entire frame.
   189  			q.consume(1)
   190  		}
   191  		qp.put(q)
   192  	}
   193  }
   194  

View as plain text