Skip to content

Commit

Permalink
dmz: use sendfile(2) when cloning /proc/self/exe
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
cyphar committed Sep 22, 2023
1 parent f8348f6 commit 90c8d36
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 1 deletion.
2 changes: 1 addition & 1 deletion libcontainer/dmz/cloned_binary_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func CloneBinary(src io.Reader, size int64, name, tmpDir string) (*os.File, erro
if err != nil {
return nil, err
}
copied, err := io.Copy(file, src)
copied, err := system.Copy(file, src)
if err != nil {
file.Close()
return nil, fmt.Errorf("copy binary: %w", err)
Expand Down
40 changes: 40 additions & 0 deletions libcontainer/system/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package system

import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
Expand Down Expand Up @@ -174,3 +175,42 @@ func ExecutableMemfd(comment string, flags int) (*os.File, error) {
}
return os.NewFile(uintptr(memfd), "/memfd:"+comment), nil
}

// Copy is like io.Copy except it uses sendfile(2) if the source and sink are
// both (*os.File) as an optimisation to make copies faster.
func Copy(dst io.Writer, src io.Reader) (copied int64, err error) {
dstFile, _ := dst.(*os.File)
srcFile, _ := src.(*os.File)

if dstFile != nil && srcFile != nil {
fi, err := srcFile.Stat()
if err != nil {
goto fallback
}
size := fi.Size()
for size > 0 {
n, err := unix.Sendfile(int(dstFile.Fd()), int(srcFile.Fd()), nil, int(size))
if n > 0 {
size -= int64(n)
copied += int64(n)
}
if err == unix.EINTR {
continue
}
if err != nil {
if copied == 0 {
// If we haven't copied anything so far, we can safely just
// fallback to io.Copy. We could always do the fallback but
// it's safer to error out in the case of a partial copy
// followed by an error (which should never happen).
goto fallback
}
return copied, fmt.Errorf("partial sendfile copy: %w", err)
}
}
return copied, nil
}

fallback:
return io.Copy(dst, src)
}

0 comments on commit 90c8d36

Please sign in to comment.