From 3b1fc93fc15c645820aa4a7d912e824f459ea1e3 Mon Sep 17 00:00:00 2001 From: Eric Lagergren Date: Wed, 14 Dec 2022 22:19:58 -0800 Subject: [PATCH] unix: avoid allocations for common uses of Readv, Writev, etc. Fixes golang/go#57296 Change-Id: Ifd57487122a590df467e97e2d35f388a58cc080d Reviewed-on: https://go-review.googlesource.com/c/sys/+/457815 Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Auto-Submit: Ian Lance Taylor Reviewed-by: Benny Siegert Reviewed-by: Matt Layher --- unix/syscall_linux.go | 42 ++++++++++++++++++++++++++------------ unix/syscall_linux_test.go | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 73b12b68..d839962e 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1973,17 +1973,27 @@ func Signalfd(fd int, sigmask *Sigset_t, flags int) (newfd int, err error) { //sys preadv2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PREADV2 //sys pwritev2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PWRITEV2 -func bytes2iovec(bs [][]byte) []Iovec { - iovecs := make([]Iovec, len(bs)) - for i, b := range bs { - iovecs[i].SetLen(len(b)) +// minIovec is the size of the small initial allocation used by +// Readv, Writev, etc. +// +// This small allocation gets stack allocated, which lets the +// common use case of len(iovs) <= minIovs avoid more expensive +// heap allocations. +const minIovec = 8 + +// appendBytes converts bs to Iovecs and appends them to vecs. +func appendBytes(vecs []Iovec, bs [][]byte) []Iovec { + for _, b := range bs { + var v Iovec + v.SetLen(len(b)) if len(b) > 0 { - iovecs[i].Base = &b[0] + v.Base = &b[0] } else { - iovecs[i].Base = (*byte)(unsafe.Pointer(&_zero)) + v.Base = (*byte)(unsafe.Pointer(&_zero)) } + vecs = append(vecs, v) } - return iovecs + return vecs } // offs2lohi splits offs into its low and high order bits. @@ -1993,14 +2003,16 @@ func offs2lohi(offs int64) (lo, hi uintptr) { } func Readv(fd int, iovs [][]byte) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) n, err = readv(fd, iovecs) readvRacedetect(iovecs, n, err) return n, err } func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) lo, hi := offs2lohi(offset) n, err = preadv(fd, iovecs, lo, hi) readvRacedetect(iovecs, n, err) @@ -2008,7 +2020,8 @@ func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) { } func Preadv2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) lo, hi := offs2lohi(offset) n, err = preadv2(fd, iovecs, lo, hi, flags) readvRacedetect(iovecs, n, err) @@ -2035,7 +2048,8 @@ func readvRacedetect(iovecs []Iovec, n int, err error) { } func Writev(fd int, iovs [][]byte) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) } @@ -2045,7 +2059,8 @@ func Writev(fd int, iovs [][]byte) (n int, err error) { } func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) } @@ -2056,7 +2071,8 @@ func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) { } func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) { - iovecs := bytes2iovec(iovs) + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) if raceenabled { raceReleaseMerge(unsafe.Pointer(&ioSync)) } diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 170e37ee..2787fe57 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -1132,3 +1132,44 @@ func TestPwritevOffsets(t *testing.T) { t.Fatalf("expected size to be %d, got %d", want, info.Size()) } } + +func TestReadvAllocate(t *testing.T) { + f, err := os.Create(filepath.Join(t.TempDir(), "test")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { f.Close() }) + + test := func(name string, fn func(fd int)) { + n := int(testing.AllocsPerRun(100, func() { + fn(int(f.Fd())) + })) + if n != 0 { + t.Errorf("%q got %d allocations, want 0", name, n) + } + } + + iovs := make([][]byte, 8) + for i := range iovs { + iovs[i] = []byte{'A'} + } + + test("Writev", func(fd int) { + unix.Writev(fd, iovs) + }) + test("Pwritev", func(fd int) { + unix.Pwritev(fd, iovs, 0) + }) + test("Pwritev2", func(fd int) { + unix.Pwritev2(fd, iovs, 0, 0) + }) + test("Readv", func(fd int) { + unix.Readv(fd, iovs) + }) + test("Preadv", func(fd int) { + unix.Preadv(fd, iovs, 0) + }) + test("Preadv2", func(fd int) { + unix.Preadv2(fd, iovs, 0, 0) + }) +}