aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Bleecher Snyder <josharian@gmail.com>2020-11-06 17:37:03 -0800
committerJosh Bleecher Snyder <josharian@gmail.com>2021-02-25 19:42:00 +0000
commit526ee96f4992ff3a1e1c219fe8dc9870098bacba (patch)
treed7d8d1101ef6e6bfbef121be116694509e115bdf
parent194b636f8f1ff7d6b709b5b9010d1d14b3919e66 (diff)
downloadgo-526ee96f4992ff3a1e1c219fe8dc9870098bacba.tar.gz
go-526ee96f4992ff3a1e1c219fe8dc9870098bacba.zip
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 <josharian@gmail.com> Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
-rw-r--r--src/os/file.go9
-rw-r--r--src/os/os_test.go18
2 files changed, 26 insertions, 1 deletions
diff --git a/src/os/file.go b/src/os/file.go
index 52dd94339b..ebeb0d0ac9 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 a32e5fc11e..f27c796c05 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)
+ }
+}