Skip to content

Commit 90c8d36

Browse files
committed
dmz: use sendfile(2) when cloning /proc/self/exe
This results in a 5-20% speedup of dmz.CloneBinary(), depending on the machine. io.Copy: goos: linux goarch: amd64 pkg: github.com/opencontainers/runc/libcontainer/dmz cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz BenchmarkCloneBinary BenchmarkCloneBinary-8 139 8075074 ns/op PASS ok github.com/opencontainers/runc/libcontainer/dmz 2.286s unix.Sendfile: goos: linux goarch: amd64 pkg: github.com/opencontainers/runc/libcontainer/dmz cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz BenchmarkCloneBinary BenchmarkCloneBinary-8 192 6382121 ns/op PASS ok github.com/opencontainers/runc/libcontainer/dmz 2.415s Signed-off-by: Aleksa Sarai <[email protected]>
1 parent f8348f6 commit 90c8d36

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

libcontainer/dmz/cloned_binary_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func CloneBinary(src io.Reader, size int64, name, tmpDir string) (*os.File, erro
179179
if err != nil {
180180
return nil, err
181181
}
182-
copied, err := io.Copy(file, src)
182+
copied, err := system.Copy(file, src)
183183
if err != nil {
184184
file.Close()
185185
return nil, fmt.Errorf("copy binary: %w", err)

libcontainer/system/linux.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package system
55

66
import (
77
"fmt"
8+
"io"
89
"os"
910
"os/exec"
1011
"strconv"
@@ -174,3 +175,42 @@ func ExecutableMemfd(comment string, flags int) (*os.File, error) {
174175
}
175176
return os.NewFile(uintptr(memfd), "/memfd:"+comment), nil
176177
}
178+
179+
// Copy is like io.Copy except it uses sendfile(2) if the source and sink are
180+
// both (*os.File) as an optimisation to make copies faster.
181+
func Copy(dst io.Writer, src io.Reader) (copied int64, err error) {
182+
dstFile, _ := dst.(*os.File)
183+
srcFile, _ := src.(*os.File)
184+
185+
if dstFile != nil && srcFile != nil {
186+
fi, err := srcFile.Stat()
187+
if err != nil {
188+
goto fallback
189+
}
190+
size := fi.Size()
191+
for size > 0 {
192+
n, err := unix.Sendfile(int(dstFile.Fd()), int(srcFile.Fd()), nil, int(size))
193+
if n > 0 {
194+
size -= int64(n)
195+
copied += int64(n)
196+
}
197+
if err == unix.EINTR {
198+
continue
199+
}
200+
if err != nil {
201+
if copied == 0 {
202+
// If we haven't copied anything so far, we can safely just
203+
// fallback to io.Copy. We could always do the fallback but
204+
// it's safer to error out in the case of a partial copy
205+
// followed by an error (which should never happen).
206+
goto fallback
207+
}
208+
return copied, fmt.Errorf("partial sendfile copy: %w", err)
209+
}
210+
}
211+
return copied, nil
212+
}
213+
214+
fallback:
215+
return io.Copy(dst, src)
216+
}

0 commit comments

Comments
 (0)