Repository navigation
os: zero copy pipe splice falling back to read+write #80977
Copy link
Copy link
Open
Labels
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Feedback is required from experts, contributors, and/or the community before a change can be made.Performance
Milestone
Description
Go version
go version go1.26.5 linux/amd64
Output of go env in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/arusekk/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/arusekk/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -fmessage-length=0 -ffile-prefix-map=/tmp/go-build992021822=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/arusekk/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/arusekk/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/arusekk/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.26.5'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
Running the following program:
package main
import (
"fmt"
"io"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("seq", "1", "20")
out, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("cmd.StdoutPipe err: %v\n", err)
return
}
if err := cmd.Start(); err != nil {
fmt.Printf("cmd.Start err: %v\n", err)
return
}
n, err := io.Copy(os.Stdout, out)
if err != nil {
fmt.Printf("io.Copy err: %v\n", err)
return
}
fmt.Printf("io.Copy: %v\n", n)
}go build pipesplice.go
then
strace -yy -eread,write,splice,sendfile,copy_file_range ./tryit | cat
My kernel version is 6.18.43_1; the |cat is optional, splice works equally fine for pipe->pipe and pipe->terminal (and pipe->devnull for that matter).
This problem affects workloads with heavy use of io.Copy.
What did you see happen?
copy_file_range(3<pipe:[675474]>, NULL, 1<pipe:[678331]>, NULL, 2147479552, 0) = -1 EINVAL (Invalid argument)
read(3<pipe:[675474]>, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14"..., 32768) = 51
write(1<pipe:[678331]>, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14"..., 51) = 51
read(3<pipe:[675474]>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
, "", 32768) = 0
write(1<pipe:[678331]>, "io.Copy: 51\n", 12io.Copy: 51
) = 12
+++ exited with 0 +++What did you expect to see?
After failed copy_file_range I expected Go runtime to try zero-copy using splice, producing a trace like the following:
copy_file_range(3<pipe:[675474]>, NULL, 1<pipe:[678331]>, NULL, 2147479552, 0) = -1 EINVAL (Invalid argument)
splice(3<pipe:[675474]>, NULL, 1<pipe:[678331]>, NULL, 2147479552, 0) = 51
write(1<pipe:[678331]>, "io.Copy: 51\n", 12io.Copy: 51
) = 12
+++ exited with 0 +++
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Feedback is required from experts, contributors, and/or the community before a change can be made.Performance
Activity
gabyhelp commentedon Aug 20, 2026
Related Issues
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
seankhliao commentedon Aug 22, 2026
see this comment from @panjf2000
https://go.googlesource.com/go/+/7237c87c8c679d9693c79e3d7d1efe390bc2249d/src/os/zero_copy_linux.go#69
ianlancetaylor commentedon Aug 22, 2026
We can do better there. The code handles network streams but not pipes. It could handle pipes too, which should fix this case. CC @panjf2000
panjf2000 commentedon Aug 23, 2026
Why does it matter? Like I said in those comments, I haven't run any benchmarks on the
pipe <-> pipecase, but I doubted that splice would help this case.panjf2000 commentedon Aug 23, 2026
@Arusekk
Why are you convinced that splice would help your case? Did you run benchmarks on splice against read+write? If so, please share your numbers, it would help us decide, thx.
Arusekk commentedon Aug 23, 2026
Using the following (utterly ugly) code I am consistently getting a ~1.3x improvement copying 64 GiB of zeros to /dev/null:
So the science is roughly:
Hope that helps.
panjf2000 commentedon Aug 23, 2026
Thanks for benchmarks, these numbers seem promising.
I'll try to do more benchmarks, if nothing goes wrong, I will send a CL for this.
panjf2000 commentedon Aug 25, 2026
It turns out that we didn't use splice or sendfile on pipes for a reason, check out #71375 for more context.
To sum up, there are too many quirks when using splice/sendfile on pipes, even some potential kernel bugs. We might break some existing code if we did zero-copy on pipes.
panjf2000 commentedon Aug 25, 2026
see also https://go-review.googlesource.com/c/go/+/646415