mirror of
https://github.com/amnezia-vpn/amneziawg-go.git
synced 2026-06-02 06:23:45 +02:00
test(benchmarks): add conn and conceal benchmark suites
Add benchmark harnesses for the conn and conceal packages with deterministic crypto/rand, fake transports, and no live sockets in timed loops. Keep a compact default benchmark matrix for routine benchstat runs and preserve the exhaustive matrix behind AMNEZWG_BENCH_FULL=1.
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
package conceal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
benchmarkMaxPacketSize = 65535
|
||||
benchmarkBatchPoolSize = 128
|
||||
benchmarkUDPListenPort = 51820
|
||||
benchmarkFixtureRingSize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
benchmarkFramedOpts = FramedOpts{
|
||||
H1: benchmarkMustHeader("777"),
|
||||
H2: benchmarkMustHeader("778"),
|
||||
H3: benchmarkMustHeader("779"),
|
||||
H4: benchmarkMustHeader("780"),
|
||||
S1: 8,
|
||||
S2: 8,
|
||||
S3: 8,
|
||||
S4: 16,
|
||||
}
|
||||
benchmarkFramedCompatOpts = func() FramedOpts {
|
||||
opts := benchmarkFramedOpts
|
||||
opts.HeaderCompat = true
|
||||
return opts
|
||||
}()
|
||||
benchmarkMasqueradeRules = benchmarkMustRules("<dz be 2><d>")
|
||||
benchmarkPreludeOneRule = PreludeOpts{
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xaabb>"),
|
||||
},
|
||||
}
|
||||
benchmarkPreludeFiveRules = PreludeOpts{
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xa1>"),
|
||||
benchmarkMustRules("<b 0xa2a3>"),
|
||||
benchmarkMustRules("<b 0xa4a5a6>"),
|
||||
benchmarkMustRules("<b 0xa7a8a9aa>"),
|
||||
benchmarkMustRules("<b 0xabacadaeaf>"),
|
||||
},
|
||||
}
|
||||
benchmarkPreludeRulesPlusJunk = PreludeOpts{
|
||||
Jc: 1,
|
||||
Jmin: 3,
|
||||
Jmax: 3,
|
||||
RulesArr: [5]Rules{
|
||||
benchmarkMustRules("<b 0xaabb>"),
|
||||
},
|
||||
}
|
||||
benchmarkPayloads = benchmarkBuildPayloadProfiles()
|
||||
benchmarkUDPAddr = &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: benchmarkUDPListenPort,
|
||||
}
|
||||
|
||||
benchmarkRandMu sync.Mutex
|
||||
)
|
||||
|
||||
type benchmarkPayloadProfiles struct {
|
||||
initiation []byte
|
||||
response []byte
|
||||
cookie []byte
|
||||
transportKeepalive []byte
|
||||
transportSmall []byte
|
||||
transportMTU []byte
|
||||
|
||||
compatInitiation []byte
|
||||
compatResponse []byte
|
||||
compatCookie []byte
|
||||
compatTransportKeepalive []byte
|
||||
compatTransportSmall []byte
|
||||
compatTransportMTU []byte
|
||||
}
|
||||
|
||||
func benchmarkBuildPayloadProfiles() benchmarkPayloadProfiles {
|
||||
return benchmarkPayloadProfiles{
|
||||
initiation: benchmarkMakePayload(WireguardMsgInitiationSize, WireguardMsgInitiationType),
|
||||
response: benchmarkMakePayload(WireguardMsgResponseSize, WireguardMsgResponseType),
|
||||
cookie: benchmarkMakePayload(WireguardMsgCookieReplySize, WireguardMsgCookieReplyType),
|
||||
transportKeepalive: benchmarkMakePayload(WireguardMsgTransportMinSize, WireguardMsgTransportType),
|
||||
transportSmall: benchmarkMakePayload(256, WireguardMsgTransportType),
|
||||
transportMTU: benchmarkMakePayload(1280, WireguardMsgTransportType),
|
||||
compatInitiation: benchmarkMakePayload(WireguardMsgInitiationSize, benchmarkFramedCompatOpts.H1.start),
|
||||
compatResponse: benchmarkMakePayload(WireguardMsgResponseSize, benchmarkFramedCompatOpts.H2.start),
|
||||
compatCookie: benchmarkMakePayload(WireguardMsgCookieReplySize, benchmarkFramedCompatOpts.H3.start),
|
||||
compatTransportKeepalive: benchmarkMakePayload(WireguardMsgTransportMinSize, benchmarkFramedCompatOpts.H4.start),
|
||||
compatTransportSmall: benchmarkMakePayload(256, benchmarkFramedCompatOpts.H4.start),
|
||||
compatTransportMTU: benchmarkMakePayload(1280, benchmarkFramedCompatOpts.H4.start),
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMakePayload(size int, header uint32) []byte {
|
||||
payload := make([]byte, size)
|
||||
binary.LittleEndian.PutUint32(payload[:4], header)
|
||||
for i := 4; i < len(payload); i++ {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func benchmarkMustRules(spec string) Rules {
|
||||
rules, err := ParseRules(spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func benchmarkMustHeader(spec string) *RangedHeader {
|
||||
header, err := NewRangedHeader(spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func benchmarkNewBufferPool() *sync.Pool {
|
||||
return &sync.Pool{
|
||||
New: func() any {
|
||||
return make([]byte, benchmarkMaxPacketSize)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkNewMsgsPool() *sync.Pool {
|
||||
return &sync.Pool{
|
||||
New: func() any {
|
||||
msgs := make([]ipv4.Message, benchmarkBatchPoolSize)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].OOB = make([]byte, 0)
|
||||
}
|
||||
return &msgs
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMaxFramePadding(opts FramedOpts) int {
|
||||
maxPadding := opts.S1
|
||||
if opts.S2 > maxPadding {
|
||||
maxPadding = opts.S2
|
||||
}
|
||||
if opts.S3 > maxPadding {
|
||||
maxPadding = opts.S3
|
||||
}
|
||||
if opts.S4 > maxPadding {
|
||||
maxPadding = opts.S4
|
||||
}
|
||||
return maxPadding
|
||||
}
|
||||
|
||||
func benchmarkEncodeFramedRecord(opts FramedOpts, payload []byte) []byte {
|
||||
enc, ok := newFrameEncoding(opts)
|
||||
if !ok {
|
||||
panic("framed benchmark encoding unavailable")
|
||||
}
|
||||
buf := make([]byte, len(payload)+benchmarkMaxFramePadding(opts))
|
||||
n := enc.Encode(buf, payload)
|
||||
if n == 0 {
|
||||
panic("framed benchmark encoding failed")
|
||||
}
|
||||
return append([]byte(nil), buf[:n]...)
|
||||
}
|
||||
|
||||
func benchmarkEncodeMasqueradeRecord(rules Rules, payload []byte) []byte {
|
||||
pool := benchmarkNewBufferPool()
|
||||
ctx := &writeContext{
|
||||
FlexBuffer: WrapFlexBuffer(payload),
|
||||
BufferPool: WrapBufferPool(pool),
|
||||
}
|
||||
tmp := pool.Get().([]byte)
|
||||
defer pool.Put(tmp)
|
||||
|
||||
w := bytes.NewBuffer(tmp[:0])
|
||||
if err := rules.Write(w, ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return append([]byte(nil), w.Bytes()...)
|
||||
}
|
||||
|
||||
func benchmarkEncodeStreamRecords(rules Rules, records ...[]byte) []byte {
|
||||
var out bytes.Buffer
|
||||
for _, record := range records {
|
||||
out.Write(benchmarkEncodeMasqueradeRecord(rules, record))
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
func benchmarkRepeatPayload(payload []byte, count int) [][]byte {
|
||||
payloads := make([][]byte, count)
|
||||
for i := range payloads {
|
||||
payloads[i] = payload
|
||||
}
|
||||
return payloads
|
||||
}
|
||||
|
||||
func benchmarkInitiationBatch(batchSize int) [][]byte {
|
||||
payloads := make([][]byte, batchSize)
|
||||
if batchSize == 0 {
|
||||
return payloads
|
||||
}
|
||||
payloads[0] = benchmarkPayloads.initiation
|
||||
for i := 1; i < len(payloads); i++ {
|
||||
payloads[i] = benchmarkPayloads.transportSmall
|
||||
}
|
||||
return payloads
|
||||
}
|
||||
|
||||
func benchmarkTotalBytes(payloads [][]byte) int {
|
||||
total := 0
|
||||
for _, payload := range payloads {
|
||||
total += len(payload)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func benchmarkAverageBytes(payloads ...[]byte) int {
|
||||
if len(payloads) == 0 {
|
||||
return 0
|
||||
}
|
||||
total := 0
|
||||
for _, payload := range payloads {
|
||||
total += len(payload)
|
||||
}
|
||||
return total / len(payloads)
|
||||
}
|
||||
|
||||
func benchmarkAverageInts(values ...int) int {
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
total := 0
|
||||
for _, value := range values {
|
||||
total += value
|
||||
}
|
||||
return total / len(values)
|
||||
}
|
||||
|
||||
func benchmarkFullMatrixEnabled() bool {
|
||||
return os.Getenv("AMNEZWG_BENCH_FULL") != ""
|
||||
}
|
||||
|
||||
func benchmarkRunLoop(b *testing.B, bytesPerOp int, reset func(), op func() error) {
|
||||
b.Helper()
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
reset()
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRunLoopWithFixtureRing[T any](b *testing.B, bytesPerOp int, fixtures []T, reset func(T), op func(T) error) {
|
||||
b.Helper()
|
||||
if len(fixtures) == 0 {
|
||||
b.Fatal("benchmark fixture ring must not be empty")
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%len(fixtures) == 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
for _, fixture := range fixtures {
|
||||
reset(fixture)
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(fixtures[i%len(fixtures)]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkUseDeterministicRand(b *testing.B) {
|
||||
b.Helper()
|
||||
benchmarkRandMu.Lock()
|
||||
oldReader := crand.Reader
|
||||
crand.Reader = &benchmarkDeterministicReader{state: 1}
|
||||
b.Cleanup(func() {
|
||||
crand.Reader = oldReader
|
||||
benchmarkRandMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
type benchmarkDeterministicReader struct {
|
||||
state uint64
|
||||
}
|
||||
|
||||
func (r *benchmarkDeterministicReader) Read(p []byte) (int, error) {
|
||||
x := r.state
|
||||
if x == 0 {
|
||||
x = 1
|
||||
}
|
||||
for i := range p {
|
||||
x ^= x << 13
|
||||
x ^= x >> 7
|
||||
x ^= x << 17
|
||||
p[i] = byte(x)
|
||||
}
|
||||
r.state = x
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type benchmarkStreamConn struct {
|
||||
readBuf []byte
|
||||
readOff int
|
||||
readChunks [][]byte
|
||||
readChunkAt int
|
||||
readChunkOff int
|
||||
}
|
||||
|
||||
func newBenchmarkStreamConn(readBuf []byte) *benchmarkStreamConn {
|
||||
return &benchmarkStreamConn{readBuf: readBuf}
|
||||
}
|
||||
|
||||
func newBenchmarkStreamChunksConn(readChunks [][]byte) *benchmarkStreamConn {
|
||||
return &benchmarkStreamConn{readChunks: readChunks}
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) ResetRead() {
|
||||
c.readOff = 0
|
||||
c.readChunkAt = 0
|
||||
c.readChunkOff = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Read(p []byte) (int, error) {
|
||||
if len(c.readChunks) > 0 {
|
||||
chunk := c.readChunks[c.readChunkAt]
|
||||
if c.readChunkOff >= len(chunk) {
|
||||
c.readChunkAt++
|
||||
if c.readChunkAt == len(c.readChunks) {
|
||||
c.readChunkAt = 0
|
||||
}
|
||||
c.readChunkOff = 0
|
||||
chunk = c.readChunks[c.readChunkAt]
|
||||
}
|
||||
n := copy(p, chunk[c.readChunkOff:])
|
||||
c.readChunkOff += n
|
||||
if c.readChunkOff == len(chunk) {
|
||||
c.readChunkAt++
|
||||
if c.readChunkAt == len(c.readChunks) {
|
||||
c.readChunkAt = 0
|
||||
}
|
||||
c.readChunkOff = 0
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
if len(c.readBuf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if c.readOff >= len(c.readBuf) {
|
||||
c.readOff = 0
|
||||
}
|
||||
n := copy(p, c.readBuf[c.readOff:])
|
||||
c.readOff += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) LocalAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) RemoteAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkStreamConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type benchmarkUDPConn struct {
|
||||
readPayloads [][]byte
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func newBenchmarkUDPConn(readPayloads [][]byte) *benchmarkUDPConn {
|
||||
return &benchmarkUDPConn{readPayloads: readPayloads}
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, _, _, udpAddr, err := c.ReadMsgUDP(p, nil)
|
||||
return n, udpAddr, err
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
udpAddr, _ := addr.(*net.UDPAddr)
|
||||
n, _, err = c.WriteMsgUDP(p, nil, udpAddr)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) LocalAddr() net.Addr {
|
||||
return benchmarkUDPAddr
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
if len(c.readPayloads) == 0 {
|
||||
return 0, 0, 0, nil, io.EOF
|
||||
}
|
||||
payload := c.readPayloads[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readPayloads) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
n = copy(b, payload)
|
||||
return n, 0, 0, benchmarkUDPAddr, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
return len(b), len(oob), nil
|
||||
}
|
||||
|
||||
type benchmarkBatchPacket struct {
|
||||
payload []byte
|
||||
oob []byte
|
||||
addr *net.UDPAddr
|
||||
}
|
||||
|
||||
type benchmarkBatchConn struct {
|
||||
readBatches [][]benchmarkBatchPacket
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func newBenchmarkBatchConn(readBatches [][]benchmarkBatchPacket) *benchmarkBatchConn {
|
||||
return &benchmarkBatchConn{readBatches: readBatches}
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
if len(c.readBatches) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
batch := c.readBatches[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readBatches) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
for i := range batch {
|
||||
packet := batch[i]
|
||||
msg := &ms[i]
|
||||
msg.N = copy(msg.Buffers[0], packet.payload)
|
||||
if cap(msg.OOB) < len(packet.oob) {
|
||||
panic("benchmark batch OOB buffer too small")
|
||||
}
|
||||
msg.OOB = msg.OOB[:len(packet.oob)]
|
||||
copy(msg.OOB, packet.oob)
|
||||
msg.NN = len(packet.oob)
|
||||
msg.Addr = packet.addr
|
||||
}
|
||||
for i := len(batch); i < len(ms); i++ {
|
||||
ms[i].N = 0
|
||||
ms[i].NN = 0
|
||||
ms[i].Addr = nil
|
||||
ms[i].OOB = ms[i].OOB[:0]
|
||||
}
|
||||
return len(batch), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkBatchConn) WriteBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
return len(ms), nil
|
||||
}
|
||||
|
||||
func benchmarkBatchPackets(payloads [][]byte) []benchmarkBatchPacket {
|
||||
packets := make([]benchmarkBatchPacket, len(payloads))
|
||||
for i, payload := range payloads {
|
||||
packets[i] = benchmarkBatchPacket{
|
||||
payload: payload,
|
||||
addr: benchmarkUDPAddr,
|
||||
}
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func benchmarkNewBatchReadMessages(count int, oobCap int) []ipv4.Message {
|
||||
msgs := make([]ipv4.Message, count)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].Buffers[0] = make([]byte, benchmarkMaxPacketSize)
|
||||
msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
type benchmarkBatchWriteFixture struct {
|
||||
msgs []ipv4.Message
|
||||
payloads [][]byte
|
||||
addr *net.UDPAddr
|
||||
}
|
||||
|
||||
func newBenchmarkBatchWriteFixture(payloads [][]byte, oobCap int) *benchmarkBatchWriteFixture {
|
||||
fixture := &benchmarkBatchWriteFixture{
|
||||
msgs: make([]ipv4.Message, len(payloads)),
|
||||
payloads: payloads,
|
||||
addr: benchmarkUDPAddr,
|
||||
}
|
||||
for i := range fixture.msgs {
|
||||
fixture.msgs[i].Buffers = make([][]byte, 1)
|
||||
fixture.msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
fixture.Reset()
|
||||
return fixture
|
||||
}
|
||||
|
||||
func (f *benchmarkBatchWriteFixture) Reset() {
|
||||
for i, payload := range f.payloads {
|
||||
f.msgs[i].Buffers[0] = payload
|
||||
f.msgs[i].N = len(payload)
|
||||
f.msgs[i].NN = 0
|
||||
f.msgs[i].Addr = f.addr
|
||||
f.msgs[i].OOB = f.msgs[i].OOB[:0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
package conceal
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkTCPRawConn(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPRawConnFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
readPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
}
|
||||
b.Run("Read/mixed", func(b *testing.B) {
|
||||
conn := newBenchmarkStreamChunksConn(readPayloads)
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(readPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("Write/mixed", func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(nil)
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPRawConnFull(b *testing.B) {
|
||||
readCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Read/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
}
|
||||
for _, tc := range readCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(tc.payload)
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "Write/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
conn := newBenchmarkStreamConn(nil)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPRecordMasquerade(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPRecordMasqueradeFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
readPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
}
|
||||
readEncoded := make([][]byte, len(readPayloads))
|
||||
for i, payload := range readPayloads {
|
||||
readEncoded[i] = benchmarkEncodeMasqueradeRecord(benchmarkMasqueradeRules, payload)
|
||||
}
|
||||
b.Run("ReadRecord/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(readEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(readPayloads...), nil, func() error {
|
||||
_, err := conn.ReadRecord(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("WriteRecord/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.WriteRecord(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPRecordMasqueradeFull(b *testing.B) {
|
||||
readCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "ReadRecord/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "ReadRecord/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
}
|
||||
for _, tc := range readCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.ReadRecord(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "WriteRecord/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "WriteRecord/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "WriteRecord/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected stream masquerade benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.WriteRecord(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPFramed(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPFramedFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
compatOffPayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.response,
|
||||
benchmarkPayloads.cookie,
|
||||
benchmarkPayloads.transportKeepalive,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
compatOffEncoded := make([][]byte, len(compatOffPayloads))
|
||||
for i, payload := range compatOffPayloads {
|
||||
compatOffEncoded[i] = benchmarkEncodeFramedRecord(benchmarkFramedOpts, payload)
|
||||
}
|
||||
b.Run("Read/compat_off/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(compatOffEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOffPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
b.Run("Write/compat_off/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOffPayloads...), nil, func() error {
|
||||
payload := compatOffPayloads[next]
|
||||
next++
|
||||
if next == len(compatOffPayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
compatOnPayloads := [][]byte{
|
||||
benchmarkPayloads.compatInitiation,
|
||||
benchmarkPayloads.compatTransportMTU,
|
||||
}
|
||||
compatOnEncoded := make([][]byte, len(compatOnPayloads))
|
||||
for i, payload := range compatOnPayloads {
|
||||
compatOnEncoded[i] = benchmarkEncodeFramedRecord(benchmarkFramedCompatOpts, payload)
|
||||
}
|
||||
b.Run("Read/compat_on/mixed", func(b *testing.B) {
|
||||
source := newBenchmarkStreamChunksConn(compatOnEncoded)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOnPayloads...), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
b.Run("Write/compat_on/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(compatOnPayloads...), nil, func() error {
|
||||
payload := compatOnPayloads[next]
|
||||
next++
|
||||
if next == len(compatOnPayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPFramedFull(b *testing.B) {
|
||||
readCompatOff := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/compat_off/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Read/compat_off/response", payload: benchmarkPayloads.response},
|
||||
{name: "Read/compat_off/cookie", payload: benchmarkPayloads.cookie},
|
||||
{name: "Read/compat_off/transport_keepalive", payload: benchmarkPayloads.transportKeepalive},
|
||||
{name: "Read/compat_off/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range readCompatOff {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeFramedRecord(benchmarkFramedOpts, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCompatOff := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/compat_off/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/compat_off/response", payload: benchmarkPayloads.response},
|
||||
{name: "Write/compat_off/cookie", payload: benchmarkPayloads.cookie},
|
||||
{name: "Write/compat_off/transport_keepalive", payload: benchmarkPayloads.transportKeepalive},
|
||||
{name: "Write/compat_off/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCompatOff {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected framed benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
readCompatOn := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Read/compat_on/initiation", payload: benchmarkPayloads.compatInitiation},
|
||||
{name: "Read/compat_on/transport_mtu", payload: benchmarkPayloads.compatTransportMTU},
|
||||
}
|
||||
for _, tc := range readCompatOn {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeFramedRecord(benchmarkFramedCompatOpts, tc.payload))
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(source, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
writeCompatOn := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/compat_on/initiation", payload: benchmarkPayloads.compatInitiation},
|
||||
{name: "Write/compat_on/transport_mtu", payload: benchmarkPayloads.compatTransportMTU},
|
||||
}
|
||||
for _, tc := range writeCompatOn {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
conn, ok := NewFramedConn(sink, pool, benchmarkFramedCompatOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected compat framed benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPPrelude(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPPreludeFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
b.Run("ReadCold/mixed_decoys_then_initiation", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPreludeReadFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
decoys := [][]byte{{0xaa}}
|
||||
if i%2 == 1 {
|
||||
decoys = [][]byte{{0xaa}, {0xab}, {0xac}, {0xad}, {0xae}}
|
||||
}
|
||||
fixtures[i] = newBenchmarkTCPPreludeReadFixture(decoys)
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, func(f *benchmarkTCPPreludeReadFixture) {
|
||||
f.Reset()
|
||||
}, func(f *benchmarkTCPPreludeReadFixture) error {
|
||||
return f.Read()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadHot/transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Write/initiation_mixed", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPreludeWriteFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
opts := benchmarkPreludeOneRule
|
||||
if i%2 == 1 {
|
||||
opts = benchmarkPreludeFiveRules
|
||||
}
|
||||
fixtures[i] = newBenchmarkTCPPreludeWriteFixture(
|
||||
opts,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
)
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, nil, func(f *benchmarkTCPPreludeWriteFixture) error {
|
||||
return f.Write()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Write/transport_small/passthrough", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
payload := benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall)
|
||||
benchmarkRunLoop(b, len(payload), nil, func() error {
|
||||
_, err := prelude.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPPreludeFull(b *testing.B) {
|
||||
b.Run("ReadCold/decoy1_then_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadCold/decoy5_then_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa},
|
||||
[]byte{0xab},
|
||||
[]byte{0xac},
|
||||
[]byte{0xad},
|
||||
[]byte{0xae},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ReadHot/transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := prelude.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
opts PreludeOpts
|
||||
}{
|
||||
{
|
||||
name: "Write/initiation/1_rule",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
opts: benchmarkPreludeOneRule,
|
||||
},
|
||||
{
|
||||
name: "Write/initiation/5_rules",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
opts: benchmarkPreludeFiveRules,
|
||||
},
|
||||
{
|
||||
name: "Write/transport_small/passthrough",
|
||||
payload: benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
opts: benchmarkPreludeOneRule,
|
||||
},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, tc.opts)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := prelude.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTCPPipeline(b *testing.B) {
|
||||
benchmarkUseDeterministicRand(b)
|
||||
if benchmarkFullMatrixEnabled() {
|
||||
benchmarkTCPPipelineFull(b)
|
||||
return
|
||||
}
|
||||
|
||||
b.Run("Read/full_pipeline/cold_initiation", func(b *testing.B) {
|
||||
fixtures := make([]*benchmarkTCPPipelineReadFixture, benchmarkFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = newBenchmarkTCPPipelineReadFixture([][]byte{{0xaa, 0xbb}})
|
||||
}
|
||||
benchmarkRunLoopWithFixtureRing(b, len(benchmarkPayloads.initiation), fixtures, func(f *benchmarkTCPPipelineReadFixture) {
|
||||
f.Reset()
|
||||
}, func(f *benchmarkTCPPipelineReadFixture) error {
|
||||
return f.Read()
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Read/full_pipeline/hot_transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writePayloads := [][]byte{
|
||||
benchmarkPayloads.initiation,
|
||||
benchmarkPayloads.transportSmall,
|
||||
benchmarkPayloads.transportMTU,
|
||||
}
|
||||
b.Run("Write/full_pipeline/mixed", func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
next := 0
|
||||
benchmarkRunLoop(b, benchmarkAverageBytes(writePayloads...), nil, func() error {
|
||||
payload := writePayloads[next]
|
||||
next++
|
||||
if next == len(writePayloads) {
|
||||
next = 0
|
||||
}
|
||||
_, err := conn.Write(payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkTCPPipelineFull(b *testing.B) {
|
||||
b.Run("Read/full_pipeline/cold_initiation", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
[]byte{0xaa, 0xbb},
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
reset := func() {
|
||||
source.ResetRead()
|
||||
prelude.seenValid = false
|
||||
}
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.initiation), reset, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Read/full_pipeline/hot_transport_small", func(b *testing.B) {
|
||||
pool := benchmarkNewBufferPool()
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(
|
||||
benchmarkMasqueradeRules,
|
||||
benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.transportSmall),
|
||||
))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
prelude.seenValid = true
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
buf := make([]byte, benchmarkMaxPacketSize)
|
||||
benchmarkRunLoop(b, len(benchmarkPayloads.transportSmall), nil, func() error {
|
||||
_, err := conn.Read(buf)
|
||||
return err
|
||||
})
|
||||
})
|
||||
|
||||
writeCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{name: "Write/full_pipeline/initiation", payload: benchmarkPayloads.initiation},
|
||||
{name: "Write/full_pipeline/transport_small", payload: benchmarkPayloads.transportSmall},
|
||||
{name: "Write/full_pipeline/transport_mtu", payload: benchmarkPayloads.transportMTU},
|
||||
}
|
||||
for _, tc := range writeCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
b.Fatal("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
b.Fatal("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
b.Fatal("expected pipeline benchmark conn")
|
||||
}
|
||||
benchmarkRunLoop(b, len(tc.payload), nil, func() error {
|
||||
_, err := conn.Write(tc.payload)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type benchmarkTCPPreludeReadFixture struct {
|
||||
source *benchmarkStreamConn
|
||||
prelude *PreludeConn
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPreludeReadFixture(decoys [][]byte) *benchmarkTCPPreludeReadFixture {
|
||||
pool := benchmarkNewBufferPool()
|
||||
records := make([][]byte, 0, len(decoys)+1)
|
||||
records = append(records, decoys...)
|
||||
records = append(records, benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation))
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, records...))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPreludeReadFixture{
|
||||
source: source,
|
||||
prelude: prelude,
|
||||
buf: make([]byte, benchmarkMaxPacketSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeReadFixture) Reset() {
|
||||
f.source.ResetRead()
|
||||
f.prelude.seenValid = false
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeReadFixture) Read() error {
|
||||
_, err := f.prelude.Read(f.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
type benchmarkTCPPreludeWriteFixture struct {
|
||||
prelude *PreludeConn
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPreludeWriteFixture(opts PreludeOpts, payload []byte) *benchmarkTCPPreludeWriteFixture {
|
||||
sink := newBenchmarkStreamConn(nil)
|
||||
pool := benchmarkNewBufferPool()
|
||||
recordConn, ok := NewMasqueradeConn(sink, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, opts)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPreludeWriteFixture{
|
||||
prelude: prelude,
|
||||
payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPreludeWriteFixture) Write() error {
|
||||
_, err := f.prelude.Write(f.payload)
|
||||
return err
|
||||
}
|
||||
|
||||
type benchmarkTCPPipelineReadFixture struct {
|
||||
source *benchmarkStreamConn
|
||||
prelude *PreludeConn
|
||||
conn *FramedConn
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newBenchmarkTCPPipelineReadFixture(decoys [][]byte) *benchmarkTCPPipelineReadFixture {
|
||||
pool := benchmarkNewBufferPool()
|
||||
records := make([][]byte, 0, len(decoys)+1)
|
||||
records = append(records, decoys...)
|
||||
records = append(records, benchmarkEncodeFramedRecord(benchmarkFramedOpts, benchmarkPayloads.initiation))
|
||||
source := newBenchmarkStreamConn(benchmarkEncodeStreamRecords(benchmarkMasqueradeRules, records...))
|
||||
recordConn, ok := NewMasqueradeConn(source, pool, MasqueradeOpts{
|
||||
RulesIn: benchmarkMasqueradeRules,
|
||||
RulesOut: benchmarkMasqueradeRules,
|
||||
})
|
||||
if !ok {
|
||||
panic("expected record benchmark conn")
|
||||
}
|
||||
prelude, ok := NewPreludeConn(recordConn, pool, benchmarkFramedOpts, benchmarkPreludeOneRule)
|
||||
if !ok {
|
||||
panic("expected prelude benchmark conn")
|
||||
}
|
||||
conn, ok := NewFramedConn(prelude, pool, benchmarkFramedOpts)
|
||||
if !ok {
|
||||
panic("expected pipeline benchmark conn")
|
||||
}
|
||||
return &benchmarkTCPPipelineReadFixture{
|
||||
source: source,
|
||||
prelude: prelude,
|
||||
conn: conn,
|
||||
buf: make([]byte, benchmarkMaxPacketSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPipelineReadFixture) Reset() {
|
||||
f.source.ResetRead()
|
||||
f.prelude.seenValid = false
|
||||
}
|
||||
|
||||
func (f *benchmarkTCPPipelineReadFixture) Read() error {
|
||||
_, err := f.conn.Read(f.buf)
|
||||
return err
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,620 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
const benchmarkConnMaxPacketSize = 65535
|
||||
|
||||
func BenchmarkConnEndpointDstToBytes(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ep Endpoint
|
||||
}{
|
||||
{
|
||||
name: "StdNetEndpoint/IPv4",
|
||||
ep: &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "StdNetEndpoint/IPv6",
|
||||
ep: &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "streamEndpoint/IPv4",
|
||||
ep: &streamEndpoint{
|
||||
dst: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "streamEndpoint/IPv6",
|
||||
ep: &streamEndpoint{
|
||||
dst: netip.MustParseAddrPort("[2001:db8::1]:51820"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tc.ep.DstToBytes()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnCoalesceMessages(b *testing.B) {
|
||||
addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 51820}
|
||||
ep := &StdNetEndpoint{
|
||||
AddrPort: addr.AddrPort(),
|
||||
src: make([]byte, stickyControlSize),
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
bufs [][]byte
|
||||
}{
|
||||
{
|
||||
name: "batch8/equal_transport",
|
||||
bufs: benchmarkConnEqualPayloads(8, 256),
|
||||
},
|
||||
{
|
||||
name: "batch8/mixed_transport",
|
||||
bufs: benchmarkConnMixedPayloads(8),
|
||||
},
|
||||
{
|
||||
name: "batch64/equal_transport",
|
||||
bufs: benchmarkConnEqualPayloads(64, 256),
|
||||
},
|
||||
{
|
||||
name: "batch64/mixed_transport",
|
||||
bufs: benchmarkConnMixedPayloads(64),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
msgs := benchmarkConnMessages(len(tc.bufs), 2)
|
||||
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(tc.bufs), nil, func() error {
|
||||
_ = coalesceMessages(addr, ep, tc.bufs, msgs, benchmarkConnSetGSOSize)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnSplitCoalescedMessages(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
build func() []ipv6.Message
|
||||
firstMsgAt int
|
||||
bytesPerOp int
|
||||
reset func([]ipv6.Message)
|
||||
}{
|
||||
{
|
||||
name: "gso0",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(1, 256, 0, 0) },
|
||||
firstMsgAt: 0,
|
||||
bytesPerOp: 256,
|
||||
},
|
||||
{
|
||||
name: "gso16_8msgs",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(8, 16, 16, 7) },
|
||||
firstMsgAt: 7,
|
||||
bytesPerOp: 8 * 16,
|
||||
reset: func(msgs []ipv6.Message) {
|
||||
for i := 0; i < len(msgs)-1; i++ {
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
}
|
||||
msgs[7].N = 8 * 16
|
||||
msgs[7].NN = 2
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gso128_64msgs",
|
||||
build: func() []ipv6.Message { return benchmarkConnSplitMessages(64, 128, 128, 63) },
|
||||
firstMsgAt: 63,
|
||||
bytesPerOp: 64 * 128,
|
||||
reset: func(msgs []ipv6.Message) {
|
||||
for i := 0; i < len(msgs)-1; i++ {
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
}
|
||||
msgs[63].N = 64 * 128
|
||||
msgs[63].NN = 2
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
if tc.reset == nil {
|
||||
msgs := tc.build()
|
||||
benchmarkConnRunLoop(b, tc.bytesPerOp, nil, func() error {
|
||||
_, err := splitCoalescedMessages(msgs, tc.firstMsgAt, benchmarkConnGetGSOSize)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const fixtureRingSize = 256
|
||||
fixtures := make([][]ipv6.Message, fixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = tc.build()
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if tc.bytesPerOp > 0 {
|
||||
b.SetBytes(int64(tc.bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%fixtureRingSize == 0 {
|
||||
b.StopTimer()
|
||||
for j := range fixtures {
|
||||
tc.reset(fixtures[j])
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
_, err := splitCoalescedMessages(fixtures[i%fixtureRingSize], tc.firstMsgAt, benchmarkConnGetGSOSize)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnStdNetBindSend(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
batchLen int
|
||||
offload bool
|
||||
}{
|
||||
{name: "gso_off/batch1", batchLen: 1, offload: false},
|
||||
{name: "gso_off/batch8", batchLen: 8, offload: false},
|
||||
{name: "gso_on/batch8", batchLen: 8, offload: true},
|
||||
{name: "gso_on/batch64", batchLen: 64, offload: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bind.ipv4 = &benchmarkConnUDPConn{}
|
||||
bind.ipv4PC = &benchmarkConnLinuxPacketConn{}
|
||||
bind.ipv4TxOffload = tc.offload
|
||||
|
||||
endpoint := &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("127.0.0.1:51820"),
|
||||
src: make([]byte, stickyControlSize),
|
||||
}
|
||||
|
||||
if !tc.offload {
|
||||
bufs := benchmarkConnSendPayloads(tc.batchLen, 256, false)
|
||||
benchmarkConnRunLoop(b, benchmarkConnTotalBytes(bufs), nil, func() error {
|
||||
return bind.Send(bufs, endpoint)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fixtures := make([]benchmarkConnSendFixture, benchmarkConnFixtureRingSize)
|
||||
for i := range fixtures {
|
||||
fixtures[i] = newBenchmarkConnSendFixture(tc.batchLen, 256, true)
|
||||
}
|
||||
benchmarkConnRunLoopWithFixtureRing(b, benchmarkConnTotalBytes(fixtures[0].bufs), fixtures, func(f benchmarkConnSendFixture) {
|
||||
benchmarkConnResetPayloadLens(f.bufs, f.lens)
|
||||
}, func(f benchmarkConnSendFixture) error {
|
||||
return bind.Send(f.bufs, endpoint)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnStdNetBindReceive(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
batchSize int
|
||||
rxOffload bool
|
||||
reader *benchmarkConnLinuxPacketConn
|
||||
conn *benchmarkConnUDPConn
|
||||
}{
|
||||
{
|
||||
name: "single",
|
||||
batchSize: 1,
|
||||
rxOffload: false,
|
||||
reader: &benchmarkConnLinuxPacketConn{},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "batch",
|
||||
batchSize: 8,
|
||||
rxOffload: false,
|
||||
reader: &benchmarkConnLinuxPacketConn{
|
||||
readBatches: [][]benchmarkConnPacket{benchmarkConnBatchPackets(8, 256)},
|
||||
},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "batch_rx_offload",
|
||||
batchSize: 64,
|
||||
rxOffload: true,
|
||||
reader: &benchmarkConnLinuxPacketConn{
|
||||
readBatches: [][]benchmarkConnPacket{benchmarkConnCoalescedBatch(64, 256)},
|
||||
},
|
||||
conn: &benchmarkConnUDPConn{
|
||||
readPayloads: [][]byte{benchmarkConnPayload(256)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
bufs := make([][]byte, tc.batchSize)
|
||||
for i := range bufs {
|
||||
bufs[i] = make([]byte, benchmarkConnMaxPacketSize)
|
||||
}
|
||||
sizes := make([]int, tc.batchSize)
|
||||
eps := make([]Endpoint, tc.batchSize)
|
||||
|
||||
benchmarkConnRunLoop(b, tc.batchSize*256, nil, func() error {
|
||||
_, err := bind.receiveIP(tc.reader, tc.conn, tc.rxOffload, bufs, sizes, eps)
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnRunLoop(b *testing.B, bytesPerOp int, reset func(), op func() error) {
|
||||
b.Helper()
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
reset()
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnRunLoopWithFixtureRing[T any](b *testing.B, bytesPerOp int, fixtures []T, reset func(T), op func(T) error) {
|
||||
b.Helper()
|
||||
if len(fixtures) == 0 {
|
||||
b.Fatal("benchmark fixture ring must not be empty")
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
if bytesPerOp > 0 {
|
||||
b.SetBytes(int64(bytesPerOp))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i > 0 && i%len(fixtures) == 0 && reset != nil {
|
||||
b.StopTimer()
|
||||
for _, fixture := range fixtures {
|
||||
reset(fixture)
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
if err := op(fixtures[i%len(fixtures)]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnPayload(size int) []byte {
|
||||
payload := make([]byte, size)
|
||||
for i := range payload {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func benchmarkConnEqualPayloads(count, size int) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
capacity := size
|
||||
if i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnMixedPayloads(count int) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
size := 256
|
||||
if i%2 == 1 {
|
||||
size = 1280
|
||||
}
|
||||
capacity := size
|
||||
if i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnSendPayloads(count, size int, offload bool) [][]byte {
|
||||
bufs := make([][]byte, count)
|
||||
for i := range bufs {
|
||||
capacity := size
|
||||
if offload && i == 0 {
|
||||
capacity = size * count
|
||||
}
|
||||
buf := make([]byte, size, capacity)
|
||||
for j := range buf {
|
||||
buf[j] = byte(j)
|
||||
}
|
||||
bufs[i] = buf
|
||||
}
|
||||
return bufs
|
||||
}
|
||||
|
||||
func benchmarkConnLengths(bufs [][]byte) []int {
|
||||
lens := make([]int, len(bufs))
|
||||
for i := range bufs {
|
||||
lens[i] = len(bufs[i])
|
||||
}
|
||||
return lens
|
||||
}
|
||||
|
||||
func benchmarkConnResetPayloadLens(bufs [][]byte, lens []int) {
|
||||
for i := range bufs {
|
||||
bufs[i] = bufs[i][:lens[i]]
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkConnFixtureRingSize = 256
|
||||
|
||||
type benchmarkConnSendFixture struct {
|
||||
bufs [][]byte
|
||||
lens []int
|
||||
}
|
||||
|
||||
func newBenchmarkConnSendFixture(count, size int, offload bool) benchmarkConnSendFixture {
|
||||
bufs := benchmarkConnSendPayloads(count, size, offload)
|
||||
return benchmarkConnSendFixture{
|
||||
bufs: bufs,
|
||||
lens: benchmarkConnLengths(bufs),
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnTotalBytes(bufs [][]byte) int {
|
||||
total := 0
|
||||
for _, buf := range bufs {
|
||||
total += len(buf)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func benchmarkConnMessages(count int, oobCap int) []ipv6.Message {
|
||||
msgs := make([]ipv6.Message, count)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].OOB = make([]byte, 0, oobCap)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
func benchmarkConnResetMessages(msgs []ipv6.Message) {
|
||||
for i := range msgs {
|
||||
msgs[i].Addr = nil
|
||||
msgs[i].N = 0
|
||||
msgs[i].NN = 0
|
||||
msgs[i].OOB = msgs[i].OOB[:0]
|
||||
msgs[i].Buffers[0] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkConnSetGSOSize(control *[]byte, gsoSize uint16) {
|
||||
*control = (*control)[:cap(*control)]
|
||||
binary.LittleEndian.PutUint16(*control, gsoSize)
|
||||
}
|
||||
|
||||
func benchmarkConnGetGSOSize(control []byte) (int, error) {
|
||||
if len(control) < 2 {
|
||||
return 0, nil
|
||||
}
|
||||
return int(binary.LittleEndian.Uint16(control)), nil
|
||||
}
|
||||
|
||||
func benchmarkConnSplitMessages(numMsgs, segmentSize, gsoSize, sourceIndex int) []ipv6.Message {
|
||||
msgs := make([]ipv6.Message, numMsgs)
|
||||
for i := range msgs {
|
||||
bufSize := segmentSize
|
||||
if i == sourceIndex {
|
||||
bufSize = numMsgs * segmentSize
|
||||
}
|
||||
msgs[i].Buffers = [][]byte{make([]byte, bufSize)}
|
||||
msgs[i].OOB = make([]byte, 0, 2)
|
||||
}
|
||||
for i := 0; i < numMsgs*segmentSize; i++ {
|
||||
msgs[sourceIndex].Buffers[0][i] = byte(i)
|
||||
}
|
||||
msgs[sourceIndex].N = numMsgs * segmentSize
|
||||
if gsoSize > 0 {
|
||||
msgs[sourceIndex].OOB = msgs[sourceIndex].OOB[:2]
|
||||
binary.LittleEndian.PutUint16(msgs[sourceIndex].OOB, uint16(gsoSize))
|
||||
msgs[sourceIndex].NN = 2
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
type benchmarkConnPacket struct {
|
||||
payload []byte
|
||||
oob []byte
|
||||
}
|
||||
|
||||
func benchmarkConnBatchPackets(count, size int) []benchmarkConnPacket {
|
||||
packets := make([]benchmarkConnPacket, count)
|
||||
for i := range packets {
|
||||
packets[i].payload = benchmarkConnPayload(size)
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func benchmarkConnCoalescedBatch(count, segmentSize int) []benchmarkConnPacket {
|
||||
payload := make([]byte, count*segmentSize)
|
||||
for i := range payload {
|
||||
payload[i] = byte(i)
|
||||
}
|
||||
oob := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(oob, uint16(segmentSize))
|
||||
return []benchmarkConnPacket{
|
||||
{
|
||||
payload: payload,
|
||||
oob: oob,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type benchmarkConnLinuxPacketConn struct {
|
||||
readBatches [][]benchmarkConnPacket
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
if len(c.readBatches) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
batch := c.readBatches[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readBatches) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
for i := range batch {
|
||||
msg := &ms[i]
|
||||
packet := batch[i]
|
||||
msg.N = copy(msg.Buffers[0], packet.payload)
|
||||
if cap(msg.OOB) < len(packet.oob) {
|
||||
msg.OOB = make([]byte, len(packet.oob))
|
||||
}
|
||||
msg.OOB = msg.OOB[:len(packet.oob)]
|
||||
copy(msg.OOB, packet.oob)
|
||||
msg.NN = len(packet.oob)
|
||||
msg.Addr = &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}
|
||||
}
|
||||
for i := len(batch); i < len(ms); i++ {
|
||||
ms[i].N = 0
|
||||
ms[i].NN = 0
|
||||
ms[i].Addr = nil
|
||||
ms[i].OOB = ms[i].OOB[:0]
|
||||
}
|
||||
return len(batch), nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnLinuxPacketConn) WriteBatch(ms []ipv4.Message, flags int) (int, error) {
|
||||
return len(ms), nil
|
||||
}
|
||||
|
||||
type benchmarkConnUDPConn struct {
|
||||
readPayloads [][]byte
|
||||
readIndex int
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ResetRead() {
|
||||
c.readIndex = 0
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
n, _, _, addr, err := c.ReadMsgUDP(p, nil)
|
||||
return n, addr, err
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
udpAddr, _ := addr.(*net.UDPAddr)
|
||||
n, _, err := c.WriteMsgUDP(p, nil, udpAddr)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetReadDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
if len(c.readPayloads) == 0 {
|
||||
return 0, 0, 0, nil, io.EOF
|
||||
}
|
||||
payload := c.readPayloads[c.readIndex]
|
||||
c.readIndex++
|
||||
if c.readIndex == len(c.readPayloads) {
|
||||
c.readIndex = 0
|
||||
}
|
||||
n = copy(b, payload)
|
||||
return n, 0, 0, &net.UDPAddr{
|
||||
IP: net.IPv4(127, 0, 0, 1),
|
||||
Port: 51820,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *benchmarkConnUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
|
||||
return len(b), len(oob), nil
|
||||
}
|
||||
Reference in New Issue
Block a user