From 526ee96f4992ff3a1e1c219fe8dc9870098bacba Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 6 Nov 2020 17:37:03 -0800 Subject: [PATCH] os: avoid allocation in File.WriteString Instead of alloc+copy to convert the string to a byte slice, do an unsafe conversion. Rely on the kernel not to scribble on the buffer during the write. Fixes #42406 Change-Id: I66f4838b43a11bcc3d67bbfa1706726318d55343 Reviewed-on: https://go-review.googlesource.com/c/go/+/268020 Trust: Josh Bleecher Snyder Run-TryBot: Josh Bleecher Snyder Reviewed-by: Emmanuel Odeke --- src/os/file.go | 9 ++++++++- src/os/os_test.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/os/file.go b/src/os/file.go index 52dd94339b8..ebeb0d0ac9c 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -44,11 +44,13 @@ import ( "errors" "internal/poll" "internal/testlog" + "internal/unsafeheader" "io" "io/fs" "runtime" "syscall" "time" + "unsafe" ) // Name returns the name of the file as presented to Open. @@ -246,7 +248,12 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) { // WriteString is like Write, but writes the contents of string s rather than // a slice of bytes. func (f *File) WriteString(s string) (n int, err error) { - return f.Write([]byte(s)) + var b []byte + hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b)) + hdr.Data = (*unsafeheader.String)(unsafe.Pointer(&s)).Data + hdr.Cap = len(s) + hdr.Len = len(s) + return f.Write(b) } // Mkdir creates a new directory with the specified name and permission diff --git a/src/os/os_test.go b/src/os/os_test.go index a32e5fc11ed..f27c796c057 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2773,3 +2773,21 @@ func TestReadFileProc(t *testing.T) { t.Fatalf("read %s: not newline-terminated: %q", name, data) } } + +func TestWriteStringAlloc(t *testing.T) { + if runtime.GOOS == "js" { + t.Skip("js allocates a lot during File.WriteString") + } + d := t.TempDir() + f, err := Create(filepath.Join(d, "whiteboard.txt")) + if err != nil { + t.Fatal(err) + } + defer f.Close() + allocs := testing.AllocsPerRun(100, func() { + f.WriteString("I will not allocate when passed a string longer than 32 bytes.\n") + }) + if allocs != 0 { + t.Errorf("expected 0 allocs for File.WriteString, got %v", allocs) + } +}