You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Many functions take a []byte but only read from it.
If the escape analysis code could flag parameters of a function as read-only, then code
which passes in a []byte(string) conversion could be cheaper.
bytes.IndexByte is one example.
For example, http/sniff.go does:
// -------------------
// Index of the first non-whitespace byte in data.
firstNonWS := 0
for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ {
}
func isWS(b byte) bool {
return bytes.IndexByte([]byte("\t\n\x0C\n "), b) != -1
}
// -------------------
But it's faster to avoid re-creating the []byte:
func BenchmarkIndexByteLiteral(b *testing.B) {
for i := 0; i < b.N; i++ {
IndexByte([]byte("\t\n\x0C\n "), 'x')
}
}
func BenchmarkIndexByteVariable(b *testing.B) {
var whitespace = []byte("\t\n\x0C\n ")
for i := 0; i < b.N; i++ {
IndexByte(whitespace, 'x')
}
}
bytes_test.BenchmarkIndexByteLiteral 20000000 125 ns/op
bytes_test.BenchmarkIndexByteVariable 100000000 25.4 ns/op
Related is issue #2204.
Reacted by Joe Tsai, Till Ulen, Emmanuel T Odeke, ns-cweber, Alfonso Acosta, Caleb Spare, Sergey Yarmonov, Waldemar Quevedo, Derek Perkins, Timon Wong and 22 more
It seem to be much easier to add the possibility of zerocopish taking string slices from
byte slices/arrays.
As far I understand, string slices are actually readonly and cap()'less byte slices:
http://research.swtch.com/2009/11/go-data-structures.html
I run into this often, but I should start listing examples.
In goprotobuf, text.go calls writeString with both a string and a []byte converted to a
string:
// writeAny writes an arbitrary field.
func writeAny(w *textWriter, v reflect.Value, props *Properties) {
v = reflect.Indirect(v)
// We don't attempt to serialise every possible value type; only those
// that can occur in protocol buffers, plus a few extra that were easy.
switch v.Kind() {
case reflect.Slice:
// Should only be a []byte; repeated fields are handled in writeStruct.
writeString(w, string(v.Interface().([]byte)))
case reflect.String:
writeString(w, v.String())
Note that the function writeString reallly just wants a read-only slice of bytes:
func writeString(w *textWriter, s string) {
w.WriteByte('"')
// Loop over the bytes, not the runes.
for i := 0; i < len(s); i++ {
// Divergence from C++: we don't escape apostrophes.
// There's no need to escape them, and the C++ parser
// copes with a naked apostrophe.
switch c := s[i]; c {
case '\n':
w.Write([]byte{'\\', 'n'})
case '\r':
w.Write([]byte{'\\', 'r'})
case '\t':
w.Write([]byte{'\\', 't'})
case '"':
w.Write([]byte{'\\', '"'})
case '\\':
w.Write([]byte{'\\', '\\'})
default:
if isprint(c) {
w.WriteByte(c)
} else {
fmt.Fprintf(w, "\\%03o", c)
}
}
}
w.WriteByte('"')
}
It doesn't matter that it's frozen (like a string), nor writable (like a []byte). But
Go lacks that type, so if instead it'd be nice to write writeAny with a []byte parameter
and invert the switch above to be like:
switch v.Kind() {
case reflect.Slice:
// Should only be a []byte; repeated fields are handled in writeStruct.
writeString(w, v.Interface().([]byte))
case reflect.String:
writeString(w, []byte(v.String())) // no copy!
Where the []byte(v.String()) just makes a slice header pointing in to the string's
memory, since the compiler can verify that writeAny never mutates its slice.
See patch and CL description at http://golang.org/cl/6850067 for the opposite
but very related case: strconv.ParseUint, ParseBool, etc take a string but calling code
has a []byte.
FWIW
var m map[string]int
var b []byte
_ = m[string(b)]
case must be radically simpler to implement than general read-only analysis.
It's peephole optimization, when the compiler sees such code it can generate hacky
string object using the []byte pointer.
Another example would be len(string(b)), but this seems useless.
Activity
gopherbot commentedon Aug 30, 2011
Comment 1 by jp@webmaster.ms:
bradfitz commentedon Aug 30, 2011
Comment 2:
rsc commentedon Aug 30, 2011
Comment 3:
rsc commentedon Sep 15, 2011
Comment 4:
Owner changed to @rsc.
Status changed to Accepted.
lvdlvd commentedon Nov 7, 2011
Comment 5:
Labels changed: added compilerbug, performance.
rsc commentedon Dec 9, 2011
Comment 6:
Labels changed: added priority-later.
rsc commentedon Sep 12, 2012
Comment 8:
Labels changed: added go1.1maybe.
bradfitz commentedon Oct 25, 2012
Comment 9:
I run into this often, but I should start listing examples. In goprotobuf, text.go calls writeString with both a string and a []byte converted to a string: // writeAny writes an arbitrary field. func writeAny(w *textWriter, v reflect.Value, props *Properties) { v = reflect.Indirect(v) // We don't attempt to serialise every possible value type; only those // that can occur in protocol buffers, plus a few extra that were easy. switch v.Kind() { case reflect.Slice: // Should only be a []byte; repeated fields are handled in writeStruct. writeString(w, string(v.Interface().([]byte))) case reflect.String: writeString(w, v.String()) Note that the function writeString reallly just wants a read-only slice of bytes: func writeString(w *textWriter, s string) { w.WriteByte('"') // Loop over the bytes, not the runes. for i := 0; i < len(s); i++ { // Divergence from C++: we don't escape apostrophes. // There's no need to escape them, and the C++ parser // copes with a naked apostrophe. switch c := s[i]; c { case '\n': w.Write([]byte{'\\', 'n'}) case '\r': w.Write([]byte{'\\', 'r'}) case '\t': w.Write([]byte{'\\', 't'}) case '"': w.Write([]byte{'\\', '"'}) case '\\': w.Write([]byte{'\\', '\\'}) default: if isprint(c) { w.WriteByte(c) } else { fmt.Fprintf(w, "\\%03o", c) } } } w.WriteByte('"') } It doesn't matter that it's frozen (like a string), nor writable (like a []byte). But Go lacks that type, so if instead it'd be nice to write writeAny with a []byte parameter and invert the switch above to be like: switch v.Kind() { case reflect.Slice: // Should only be a []byte; repeated fields are handled in writeStruct. writeString(w, v.Interface().([]byte)) case reflect.String: writeString(w, []byte(v.String())) // no copy! Where the []byte(v.String()) just makes a slice header pointing in to the string's memory, since the compiler can verify that writeAny never mutates its slice.bradfitz commentedon Nov 17, 2012
Comment 10:
robpike commentedon Mar 7, 2013
Comment 11:
Labels changed: removed go1.1maybe.
rsc commentedon Mar 12, 2013
Comment 12:
bradfitz commentedon Mar 31, 2013
Comment 13:
dvyukov commentedon Mar 31, 2013
Comment 14:
bradfitz commentedon Mar 31, 2013
Comment 15:
bradfitz commentedon Apr 26, 2013
Comment 16:
Labels changed: added garbage.
50 remaining items
gopherbot commentedon Aug 18, 2023
Change https://go.dev/cl/520600 mentions this issue:
cmd/compile: enable -d=zerocopy by defaultjfcg commentedon Aug 18, 2023
Hi,
These changes address redundant copying for
string <--> []byteconversions both ways, right?mdempsky commentedon Aug 18, 2023
@jfcg No, at this time only string->[]byte.
cespare commentedon Aug 18, 2023
I believe that #43752 discusses at least some of the
[]byte->stringcases.(@mdempsky it's exciting that this 4-digit issue is so close to being solved!)
cmd/compile: restore zero-copy string->[]byte optimization
zigo101 commentedon Nov 28, 2023
BTW, should the old optimization for
for range []byte(aString)be unified into the new optimization? Maybe it has been?cmd/compile: enable zero-copy string->[]byte conversions
cmd/compile: enable -d=zerocopy by default
gopherbot commentedon May 8, 2024
Change https://go.dev/cl/584118 mentions this issue:
encoding/hex: don't overallocate memory in DecodeStringencoding/hex: don't overallocate memory in DecodeString
zigo101 commentedon Jun 23, 2024
So now we can take string element addresses without
unsafe.StringData:;D
dumper: don't rely on the common package