]> Cypherpunks repositories - gostls13.git/commitdiff
net: support benchmark testing for sendfile on various platforms
authorAndy Pan <panjf2000@gmail.com>
Sat, 18 Nov 2023 10:59:51 +0000 (18:59 +0800)
committerGopher Robot <gobot@golang.org>
Wed, 7 Feb 2024 20:33:40 +0000 (20:33 +0000)
When I introduced the benchmark test code for sendfile(2) in CL 425878,
I only did it on Linux while the sendfile system call is also available
on other Unix-like and Windows platforms, this CL will pick up where I left out.

goos: darwin
goarch: arm64
pkg: net
BenchmarkSendFile/file-to-tcp/1024-10      2240488        749.5 ns/op 1366.30 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/2048-10      1956669        850.4 ns/op 2408.38 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/4096-10       840103       1593 ns/op 2571.30 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/8192-10       449536       2881 ns/op 2843.35 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/16384-10      269974       6307 ns/op 2597.86 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/32768-10      137210      12646 ns/op 2591.09 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/65536-10       66642      24557 ns/op 2668.74 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/131072-10      37852      59550 ns/op 2201.03 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/262144-10      16288     107859 ns/op 2430.44 MB/s        2 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/524288-10      10540     249957 ns/op 2097.52 MB/s        3 B/op        0 allocs/op
BenchmarkSendFile/file-to-tcp/1048576-10      4982     419750 ns/op 2498.09 MB/s        6 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/1024-10     1180185       1187 ns/op  862.66 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/2048-10      523159       2294 ns/op  892.78 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/4096-10      238792       5223 ns/op  784.29 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/8192-10      116611      10929 ns/op  749.58 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/16384-10      57568      19870 ns/op  824.57 MB/s        0 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/32768-10      32280      33696 ns/op  972.47 MB/s        1 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/65536-10      17242      72122 ns/op  908.69 MB/s        1 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/131072-10      8350     159131 ns/op  823.67 MB/s        3 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/262144-10      3872     318000 ns/op  824.35 MB/s        8 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/524288-10      1766     600785 ns/op  872.67 MB/s       18 B/op        0 allocs/op
BenchmarkSendFile/file-to-unix/1048576-10            993    1138624 ns/op  920.92 MB/s       33 B/op        0 allocs/op

goos: linux
goarch: amd64
pkg: net
cpu: DO-Premium-AMD
BenchmarkSendFile/file-to-tcp/1024-8             1796002               716.3 ns/op      1429.59 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/2048-8             1196700               896.6 ns/op      2284.23 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/4096-8              923604              2385 ns/op        1717.48 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/8192-8              638967              9722 ns/op         842.60 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/16384-8             357740             18710 ns/op         875.67 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/32768-8             147417             17489 ns/op        1873.66 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/65536-8             113054             58818 ns/op        1114.21 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/131072-8             57981            113202 ns/op        1157.86 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/262144-8             26362            253376 ns/op        1034.61 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/524288-8             13767            442053 ns/op        1186.03 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-tcp/1048576-8             4906            829984 ns/op        1263.37 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/1024-8            2031691               628.9 ns/op      1628.36 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/2048-8            1294472               965.0 ns/op      2122.30 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/4096-8            1005753              1203 ns/op        3404.27 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/8192-8             865448              6412 ns/op        1277.65 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/16384-8            268946             12801 ns/op        1279.89 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/32768-8            153398              6691 ns/op        4897.23 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/65536-8             88911             11969 ns/op        5475.36 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/131072-8            48639            107538 ns/op        1218.84 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/262144-8            22720            203199 ns/op        1290.09 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/524288-8            12034             97126 ns/op        5398.03 MB/s           0 B/op          0 allocs/op
BenchmarkSendFile/file-to-unix/1048576-8            5374            202308 ns/op        5183.06 MB/s           0 B/op          0 allocs/op

Change-Id: Ib9507bd9837ecb38b1702afa89502da18806929c
Reviewed-on: https://go-review.googlesource.com/c/go/+/543276
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/net/mockserver_test.go
src/net/sendfile_linux_test.go [deleted file]
src/net/sendfile_test.go
src/net/splice_linux_test.go

index 46b2a57321107b4d9ea2c8aeec892a6ea61e65eb..d4bd16e4c681f572ecc64fd9631e2d4a6d52331b 100644 (file)
@@ -8,8 +8,11 @@ import (
        "context"
        "errors"
        "fmt"
+       "internal/testenv"
+       "log"
        "os"
        "path/filepath"
+       "strconv"
        "sync"
        "testing"
        "time"
@@ -506,3 +509,118 @@ func packetTransceiver(c PacketConn, wb []byte, dst Addr, ch chan<- error) {
                ch <- fmt.Errorf("read %d; want %d", n, len(wb))
        }
 }
+
+func spawnTestSocketPair(t testing.TB, net string) (client, server Conn) {
+       t.Helper()
+       ln := newLocalListener(t, net)
+       defer ln.Close()
+       var cerr, serr error
+       acceptDone := make(chan struct{})
+       go func() {
+               server, serr = ln.Accept()
+               acceptDone <- struct{}{}
+       }()
+       client, cerr = Dial(ln.Addr().Network(), ln.Addr().String())
+       <-acceptDone
+       if cerr != nil {
+               if server != nil {
+                       server.Close()
+               }
+               t.Fatal(cerr)
+       }
+       if serr != nil {
+               if client != nil {
+                       client.Close()
+               }
+               t.Fatal(serr)
+       }
+       return client, server
+}
+
+func startTestSocketPeer(t testing.TB, conn Conn, op string, chunkSize, totalSize int) (func(t testing.TB), error) {
+       f, err := conn.(interface{ File() (*os.File, error) }).File()
+       if err != nil {
+               return nil, err
+       }
+
+       cmd := testenv.Command(t, os.Args[0])
+       cmd.Env = []string{
+               "GO_NET_TEST_TRANSFER=1",
+               "GO_NET_TEST_TRANSFER_OP=" + op,
+               "GO_NET_TEST_TRANSFER_CHUNK_SIZE=" + strconv.Itoa(chunkSize),
+               "GO_NET_TEST_TRANSFER_TOTAL_SIZE=" + strconv.Itoa(totalSize),
+               "TMPDIR=" + os.Getenv("TMPDIR"),
+       }
+       cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+       cmd.Stdout = os.Stdout
+       cmd.Stderr = os.Stderr
+
+       if err := cmd.Start(); err != nil {
+               return nil, err
+       }
+
+       cmdCh := make(chan error, 1)
+       go func() {
+               err := cmd.Wait()
+               conn.Close()
+               f.Close()
+               cmdCh <- err
+       }()
+
+       return func(tb testing.TB) {
+               err := <-cmdCh
+               if err != nil {
+                       tb.Errorf("process exited with error: %v", err)
+               }
+       }, nil
+}
+
+func init() {
+       if os.Getenv("GO_NET_TEST_TRANSFER") == "" {
+               return
+       }
+       defer os.Exit(0)
+
+       f := os.NewFile(uintptr(3), "splice-test-conn")
+       defer f.Close()
+
+       conn, err := FileConn(f)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       var chunkSize int
+       if chunkSize, err = strconv.Atoi(os.Getenv("GO_NET_TEST_TRANSFER_CHUNK_SIZE")); err != nil {
+               log.Fatal(err)
+       }
+       buf := make([]byte, chunkSize)
+
+       var totalSize int
+       if totalSize, err = strconv.Atoi(os.Getenv("GO_NET_TEST_TRANSFER_TOTAL_SIZE")); err != nil {
+               log.Fatal(err)
+       }
+
+       var fn func([]byte) (int, error)
+       switch op := os.Getenv("GO_NET_TEST_TRANSFER_OP"); op {
+       case "r":
+               fn = conn.Read
+       case "w":
+               defer conn.Close()
+
+               fn = conn.Write
+       default:
+               log.Fatalf("unknown op %q", op)
+       }
+
+       var n int
+       for count := 0; count < totalSize; count += n {
+               if count+chunkSize > totalSize {
+                       buf = buf[:totalSize-count]
+               }
+
+               var err error
+               if n, err = fn(buf); err != nil {
+                       return
+               }
+       }
+}
diff --git a/src/net/sendfile_linux_test.go b/src/net/sendfile_linux_test.go
deleted file mode 100644 (file)
index 7a66d36..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build linux
-
-package net
-
-import (
-       "io"
-       "os"
-       "strconv"
-       "testing"
-)
-
-func BenchmarkSendFile(b *testing.B) {
-       b.Run("file-to-tcp", func(b *testing.B) { benchmarkSendFile(b, "tcp") })
-       b.Run("file-to-unix", func(b *testing.B) { benchmarkSendFile(b, "unix") })
-}
-
-func benchmarkSendFile(b *testing.B, proto string) {
-       for i := 0; i <= 10; i++ {
-               size := 1 << (i + 10)
-               bench := sendFileBench{
-                       proto:     proto,
-                       chunkSize: size,
-               }
-               b.Run(strconv.Itoa(size), bench.benchSendFile)
-       }
-}
-
-type sendFileBench struct {
-       proto     string
-       chunkSize int
-}
-
-func (bench sendFileBench) benchSendFile(b *testing.B) {
-       fileSize := b.N * bench.chunkSize
-       f := createTempFile(b, fileSize)
-
-       client, server := spliceTestSocketPair(b, bench.proto)
-       defer server.Close()
-
-       cleanUp, err := startSpliceClient(client, "r", bench.chunkSize, fileSize)
-       if err != nil {
-               client.Close()
-               b.Fatal(err)
-       }
-       defer cleanUp()
-
-       b.ReportAllocs()
-       b.SetBytes(int64(bench.chunkSize))
-       b.ResetTimer()
-
-       // Data go from file to socket via sendfile(2).
-       sent, err := io.Copy(server, f)
-       if err != nil {
-               b.Fatalf("failed to copy data with sendfile, error: %v", err)
-       }
-       if sent != int64(fileSize) {
-               b.Fatalf("bytes sent mismatch, got: %d, want: %d", sent, fileSize)
-       }
-}
-
-func createTempFile(b *testing.B, size int) *os.File {
-       f, err := os.CreateTemp(b.TempDir(), "linux-sendfile-bench")
-       if err != nil {
-               b.Fatalf("failed to create temporary file: %v", err)
-       }
-       b.Cleanup(func() {
-               f.Close()
-       })
-
-       data := make([]byte, size)
-       if _, err := f.Write(data); err != nil {
-               b.Fatalf("failed to create and feed the file: %v", err)
-       }
-       if err := f.Sync(); err != nil {
-               b.Fatalf("failed to save the file: %v", err)
-       }
-       if _, err := f.Seek(0, io.SeekStart); err != nil {
-               b.Fatalf("failed to rewind the file: %v", err)
-       }
-
-       return f
-}
index 4cba1ed2b1620a9c041d88af2949138a59da8803..c3d5e714bde1a09a478723ca51b00bf992c6acb3 100644 (file)
@@ -14,6 +14,7 @@ import (
        "io"
        "os"
        "runtime"
+       "strconv"
        "sync"
        "testing"
        "time"
@@ -446,3 +447,75 @@ func BenchmarkSendfileZeroBytes(b *testing.B) {
 
        cancel()
 }
+
+func BenchmarkSendFile(b *testing.B) {
+       b.Run("file-to-tcp", func(b *testing.B) { benchmarkSendFile(b, "tcp") })
+       b.Run("file-to-unix", func(b *testing.B) { benchmarkSendFile(b, "unix") })
+}
+
+func benchmarkSendFile(b *testing.B, proto string) {
+       for i := 0; i <= 10; i++ {
+               size := 1 << (i + 10)
+               bench := sendFileBench{
+                       proto:     proto,
+                       chunkSize: size,
+               }
+               b.Run(strconv.Itoa(size), bench.benchSendFile)
+       }
+}
+
+type sendFileBench struct {
+       proto     string
+       chunkSize int
+}
+
+func (bench sendFileBench) benchSendFile(b *testing.B) {
+       fileSize := b.N * bench.chunkSize
+       f := createTempFile(b, fileSize)
+
+       client, server := spawnTestSocketPair(b, bench.proto)
+       defer server.Close()
+
+       cleanUp, err := startTestSocketPeer(b, client, "r", bench.chunkSize, fileSize)
+       if err != nil {
+               client.Close()
+               b.Fatal(err)
+       }
+       defer cleanUp(b)
+
+       b.ReportAllocs()
+       b.SetBytes(int64(bench.chunkSize))
+       b.ResetTimer()
+
+       // Data go from file to socket via sendfile(2).
+       sent, err := io.Copy(server, f)
+       if err != nil {
+               b.Fatalf("failed to copy data with sendfile, error: %v", err)
+       }
+       if sent != int64(fileSize) {
+               b.Fatalf("bytes sent mismatch, got: %d, want: %d", sent, fileSize)
+       }
+}
+
+func createTempFile(b *testing.B, size int) *os.File {
+       f, err := os.CreateTemp(b.TempDir(), "sendfile-bench")
+       if err != nil {
+               b.Fatalf("failed to create temporary file: %v", err)
+       }
+       b.Cleanup(func() {
+               f.Close()
+       })
+
+       data := make([]byte, size)
+       if _, err := f.Write(data); err != nil {
+               b.Fatalf("failed to create and feed the file: %v", err)
+       }
+       if err := f.Sync(); err != nil {
+               b.Fatalf("failed to save the file: %v", err)
+       }
+       if _, err := f.Seek(0, io.SeekStart); err != nil {
+               b.Fatalf("failed to rewind the file: %v", err)
+       }
+
+       return f
+}
index 7082ecdfbe82dcfe7ef39afaa4719a705527272c..2edd7444068b94c4bb0bd04ff2770044edda0838 100644 (file)
@@ -9,14 +9,11 @@ package net
 import (
        "internal/poll"
        "io"
-       "log"
        "os"
-       "os/exec"
        "strconv"
        "sync"
        "syscall"
        "testing"
-       "time"
 )
 
 func TestSplice(t *testing.T) {
@@ -62,30 +59,33 @@ type spliceTestCase struct {
 func (tc spliceTestCase) test(t *testing.T) {
        hook := hookSplice(t)
 
-       clientUp, serverUp := spliceTestSocketPair(t, tc.upNet)
+       // We need to use the actual size for startTestSocketPeer when testing with LimitedReader,
+       // otherwise the child process created in startTestSocketPeer will hang infinitely because of
+       // the mismatch of data size to transfer.
+       size := tc.totalSize
+       if tc.limitReadSize > 0 {
+               if tc.limitReadSize < size {
+                       size = tc.limitReadSize
+               }
+       }
+
+       clientUp, serverUp := spawnTestSocketPair(t, tc.upNet)
        defer serverUp.Close()
-       cleanup, err := startSpliceClient(clientUp, "w", tc.chunkSize, tc.totalSize)
+       cleanup, err := startTestSocketPeer(t, clientUp, "w", tc.chunkSize, size)
        if err != nil {
                t.Fatal(err)
        }
-       defer cleanup()
-       clientDown, serverDown := spliceTestSocketPair(t, tc.downNet)
+       defer cleanup(t)
+       clientDown, serverDown := spawnTestSocketPair(t, tc.downNet)
        defer serverDown.Close()
-       cleanup, err = startSpliceClient(clientDown, "r", tc.chunkSize, tc.totalSize)
+       cleanup, err = startTestSocketPeer(t, clientDown, "r", tc.chunkSize, size)
        if err != nil {
                t.Fatal(err)
        }
-       defer cleanup()
+       defer cleanup(t)
 
-       var (
-               r    io.Reader = serverUp
-               size           = tc.totalSize
-       )
+       var r io.Reader = serverUp
        if tc.limitReadSize > 0 {
-               if tc.limitReadSize < size {
-                       size = tc.limitReadSize
-               }
-
                r = &io.LimitedReader{
                        N: int64(tc.limitReadSize),
                        R: serverUp,
@@ -167,31 +167,34 @@ func verifySpliceFds(t *testing.T, c Conn, hook *spliceHook, fdType string) {
 func (tc spliceTestCase) testFile(t *testing.T) {
        hook := hookSplice(t)
 
+       // We need to use the actual size for startTestSocketPeer when testing with LimitedReader,
+       // otherwise the child process created in startTestSocketPeer will hang infinitely because of
+       // the mismatch of data size to transfer.
+       actualSize := tc.totalSize
+       if tc.limitReadSize > 0 {
+               if tc.limitReadSize < actualSize {
+                       actualSize = tc.limitReadSize
+               }
+       }
+
        f, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
        if err != nil {
                t.Fatal(err)
        }
        defer f.Close()
 
-       client, server := spliceTestSocketPair(t, tc.upNet)
+       client, server := spawnTestSocketPair(t, tc.upNet)
        defer server.Close()
 
-       cleanup, err := startSpliceClient(client, "w", tc.chunkSize, tc.totalSize)
+       cleanup, err := startTestSocketPeer(t, client, "w", tc.chunkSize, actualSize)
        if err != nil {
                client.Close()
                t.Fatal("failed to start splice client:", err)
        }
-       defer cleanup()
+       defer cleanup(t)
 
-       var (
-               r          io.Reader = server
-               actualSize           = tc.totalSize
-       )
+       var r io.Reader = server
        if tc.limitReadSize > 0 {
-               if tc.limitReadSize < actualSize {
-                       actualSize = tc.limitReadSize
-               }
-
                r = &io.LimitedReader{
                        N: int64(tc.limitReadSize),
                        R: r,
@@ -234,9 +237,9 @@ func testSpliceReaderAtEOF(t *testing.T, upNet, downNet string) {
 
        hook := hookSplice(t)
 
-       clientUp, serverUp := spliceTestSocketPair(t, upNet)
+       clientUp, serverUp := spawnTestSocketPair(t, upNet)
        defer clientUp.Close()
-       clientDown, serverDown := spliceTestSocketPair(t, downNet)
+       clientDown, serverDown := spawnTestSocketPair(t, downNet)
        defer clientDown.Close()
        defer serverDown.Close()
 
@@ -343,10 +346,10 @@ func testSpliceIssue25985(t *testing.T, upNet, downNet string) {
 }
 
 func testSpliceNoUnixpacket(t *testing.T) {
-       clientUp, serverUp := spliceTestSocketPair(t, "unixpacket")
+       clientUp, serverUp := spawnTestSocketPair(t, "unixpacket")
        defer clientUp.Close()
        defer serverUp.Close()
-       clientDown, serverDown := spliceTestSocketPair(t, "tcp")
+       clientDown, serverDown := spawnTestSocketPair(t, "tcp")
        defer clientDown.Close()
        defer serverDown.Close()
        // If splice called poll.Splice here, we'd get err == syscall.EINVAL
@@ -374,7 +377,7 @@ func testSpliceNoUnixgram(t *testing.T) {
                t.Fatal(err)
        }
        defer up.Close()
-       clientDown, serverDown := spliceTestSocketPair(t, "tcp")
+       clientDown, serverDown := spawnTestSocketPair(t, "tcp")
        defer clientDown.Close()
        defer serverDown.Close()
        // Analogous to testSpliceNoUnixpacket.
@@ -409,23 +412,23 @@ func (tc spliceTestCase) bench(b *testing.B) {
        // To benchmark the genericReadFrom code path, set this to false.
        useSplice := true
 
-       clientUp, serverUp := spliceTestSocketPair(b, tc.upNet)
+       clientUp, serverUp := spawnTestSocketPair(b, tc.upNet)
        defer serverUp.Close()
 
-       cleanup, err := startSpliceClient(clientUp, "w", tc.chunkSize, tc.chunkSize*b.N)
+       cleanup, err := startTestSocketPeer(b, clientUp, "w", tc.chunkSize, tc.chunkSize*b.N)
        if err != nil {
                b.Fatal(err)
        }
-       defer cleanup()
+       defer cleanup(b)
 
-       clientDown, serverDown := spliceTestSocketPair(b, tc.downNet)
+       clientDown, serverDown := spawnTestSocketPair(b, tc.downNet)
        defer serverDown.Close()
 
-       cleanup, err = startSpliceClient(clientDown, "r", tc.chunkSize, tc.chunkSize*b.N)
+       cleanup, err = startTestSocketPeer(b, clientDown, "r", tc.chunkSize, tc.chunkSize*b.N)
        if err != nil {
                b.Fatal(err)
        }
-       defer cleanup()
+       defer cleanup(b)
 
        b.SetBytes(int64(tc.chunkSize))
        b.ResetTimer()
@@ -446,128 +449,6 @@ func (tc spliceTestCase) bench(b *testing.B) {
        }
 }
 
-func spliceTestSocketPair(t testing.TB, net string) (client, server Conn) {
-       t.Helper()
-       ln := newLocalListener(t, net)
-       defer ln.Close()
-       var cerr, serr error
-       acceptDone := make(chan struct{})
-       go func() {
-               server, serr = ln.Accept()
-               acceptDone <- struct{}{}
-       }()
-       client, cerr = Dial(ln.Addr().Network(), ln.Addr().String())
-       <-acceptDone
-       if cerr != nil {
-               if server != nil {
-                       server.Close()
-               }
-               t.Fatal(cerr)
-       }
-       if serr != nil {
-               if client != nil {
-                       client.Close()
-               }
-               t.Fatal(serr)
-       }
-       return client, server
-}
-
-func startSpliceClient(conn Conn, op string, chunkSize, totalSize int) (func(), error) {
-       f, err := conn.(interface{ File() (*os.File, error) }).File()
-       if err != nil {
-               return nil, err
-       }
-
-       cmd := exec.Command(os.Args[0], os.Args[1:]...)
-       cmd.Env = []string{
-               "GO_NET_TEST_SPLICE=1",
-               "GO_NET_TEST_SPLICE_OP=" + op,
-               "GO_NET_TEST_SPLICE_CHUNK_SIZE=" + strconv.Itoa(chunkSize),
-               "GO_NET_TEST_SPLICE_TOTAL_SIZE=" + strconv.Itoa(totalSize),
-               "TMPDIR=" + os.Getenv("TMPDIR"),
-       }
-       cmd.ExtraFiles = append(cmd.ExtraFiles, f)
-       cmd.Stdout = os.Stdout
-       cmd.Stderr = os.Stderr
-
-       if err := cmd.Start(); err != nil {
-               return nil, err
-       }
-
-       donec := make(chan struct{})
-       go func() {
-               cmd.Wait()
-               conn.Close()
-               f.Close()
-               close(donec)
-       }()
-
-       return func() {
-               select {
-               case <-donec:
-               case <-time.After(5 * time.Second):
-                       log.Printf("killing splice client after 5 second shutdown timeout")
-                       cmd.Process.Kill()
-                       select {
-                       case <-donec:
-                       case <-time.After(5 * time.Second):
-                               log.Printf("splice client didn't die after 10 seconds")
-                       }
-               }
-       }, nil
-}
-
-func init() {
-       if os.Getenv("GO_NET_TEST_SPLICE") == "" {
-               return
-       }
-       defer os.Exit(0)
-
-       f := os.NewFile(uintptr(3), "splice-test-conn")
-       defer f.Close()
-
-       conn, err := FileConn(f)
-       if err != nil {
-               log.Fatal(err)
-       }
-
-       var chunkSize int
-       if chunkSize, err = strconv.Atoi(os.Getenv("GO_NET_TEST_SPLICE_CHUNK_SIZE")); err != nil {
-               log.Fatal(err)
-       }
-       buf := make([]byte, chunkSize)
-
-       var totalSize int
-       if totalSize, err = strconv.Atoi(os.Getenv("GO_NET_TEST_SPLICE_TOTAL_SIZE")); err != nil {
-               log.Fatal(err)
-       }
-
-       var fn func([]byte) (int, error)
-       switch op := os.Getenv("GO_NET_TEST_SPLICE_OP"); op {
-       case "r":
-               fn = conn.Read
-       case "w":
-               defer conn.Close()
-
-               fn = conn.Write
-       default:
-               log.Fatalf("unknown op %q", op)
-       }
-
-       var n int
-       for count := 0; count < totalSize; count += n {
-               if count+chunkSize > totalSize {
-                       buf = buf[:totalSize-count]
-               }
-
-               var err error
-               if n, err = fn(buf); err != nil {
-                       return
-               }
-       }
-}
-
 func BenchmarkSpliceFile(b *testing.B) {
        b.Run("tcp-to-file", func(b *testing.B) { benchmarkSpliceFile(b, "tcp") })
        b.Run("unix-to-file", func(b *testing.B) { benchmarkSpliceFile(b, "unix") })
@@ -598,15 +479,15 @@ func (bench spliceFileBench) benchSpliceFile(b *testing.B) {
 
        totalSize := b.N * bench.chunkSize
 
-       client, server := spliceTestSocketPair(b, bench.proto)
+       client, server := spawnTestSocketPair(b, bench.proto)
        defer server.Close()
 
-       cleanup, err := startSpliceClient(client, "w", bench.chunkSize, totalSize)
+       cleanup, err := startTestSocketPeer(b, client, "w", bench.chunkSize, totalSize)
        if err != nil {
                client.Close()
                b.Fatalf("failed to start splice client: %v", err)
        }
-       defer cleanup()
+       defer cleanup(b)
 
        b.ReportAllocs()
        b.SetBytes(int64(bench.chunkSize))