Closed
Description
The issue
Go tip doesn't know that string
/ []byte
arguments passed to functions like strings.IndexByte
don't escape. This leads to unnecessary allocation and copy in the following code:
package stringsIndex_test
import (
"strings"
"testing"
)
func BenchmarkStringsIndexByte(b *testing.B) {
bb := []byte("foobar baz")
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// string(bb) unnecessarily allocates new string and copies bb contents to it.
n := strings.IndexByte(string(bb), ' ')
Sink += n
}
}
var Sink int
Benchmark results indicate an unnecessary allocation:
BenchmarkStringsIndexByte 50000000 30.5 ns/op 16 B/op 1 allocs/op
The solution
Mark strings
and bytes
functions accepting string
/ []byte
as go:noescape
in order to eliminate unnecessary allocations in the code above.
Metadata
Metadata
Assignees
Labels
Type
Projects
Relationships
Development
No branches or pull requests
Activity
ianlancetaylor commentedon Jun 13, 2018
CC @dr2chase
I don't think we want to start using
go:noescape
in places like this. But perhaps the compiler can figure it out.valyala commentedon Jun 13, 2018
I think it will be too difficult figuring out this for functions written in assembly. Probably, it would be OK adding
go:noescape
for assembly-written functions?valyala commentedon Jun 13, 2018
This change may have significant positive impact on standard packages, since then escape analysis may prove that arguments to many functions written in Go that call
strings.Index*
and co don't escape.ianlancetaylor commentedon Jun 13, 2018
Yes, sorry, it's fine to use
go:noescape
for functions written in assembly.mundaym commentedon Jun 13, 2018
IndexByte
is already marked withgo:noescape
:go/src/internal/bytealg/indexbyte_native.go
Lines 9 to 10 in a2f72cc
I suspect this is just a case where the string cast could be cleverer.
randall77 commentedon Jun 13, 2018
I guess we could fix this, but why are you using
strings.IndexByte(string(bb), ' ')
instead ofbytes.IndexByte(bb, ' ')
?valyala commentedon Jun 15, 2018
Then the
[]byte
->string
conversion with a memory allocation and a copy for non-escaped argument looks like a bug.This is just an artificial example :)
The
strings.Index(string(b), stringPrefix)
is more real-life. It may be used interchangeably withbytes.Index(b, []byte(stringPrefix))
, since there is no a preferred code here.quasilyte commentedon Sep 19, 2018
The non-escaping
string(b)
still can allocate if it doesn't fit the small tmp buffer.I think #27148 (comment) can be relevant here.
This is a preferred way for now, since prefix is usually smaller than haystack itself, and it can probably fit fixes 32-byte stack allocated buffer.
lint: add indexAlloc checker
lint: add indexAlloc checker
quasilyte commentedon Sep 19, 2018
Here is a simple benchmarking code:
The results:
I've written a simple check that detects such calls that can be replaced with
bytes.Index
to aid in optimization sessions of big projects (https://go-critic.github.io/overview#indexAlloc-ref).lint: add indexAlloc checker (#655)
gopherbot commentedon Oct 30, 2018
Change https://golang.org/cl/146018 mentions this issue:
strings: declare Index as noescape
randall77 commentedon Oct 30, 2018
The CL I just mailed fixes
strings.Index
. I think all the other methods mentioned in the issue title are already ok. Let me know if that doesn't mesh with your understanding.