Skip to content

Commit 91102bf

Browse files
TocarIPbradfitz
authored andcommitted
runtime: use bytes.IndexByte in findnull
bytes.IndexByte is heavily optimized. Use it in findnull. This is second attempt, similar to CL97523. In this version we never call IndexByte on region of memory, that crosses page boundary. A bit slower than CL97523, but still fast: name old time/op new time/op delta GoString-6 164ns ± 2% 118ns ± 0% -28.00% (p=0.000 n=10+6) findnull is also used in gostringnocopy, which is used in many hot spots in the runtime. Fixes #23830 Change-Id: Id843dd4f65a34309d92bdd8df229e484d26b0cb2 Reviewed-on: https://go-review.googlesource.com/98015 Run-TryBot: Ilya Tocar <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 4902778 commit 91102bf

File tree

5 files changed

+112
-7
lines changed

5 files changed

+112
-7
lines changed

misc/cgo/test/basic.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ struct S {
3131
int x;
3232
};
3333
34+
const char *cstr = "abcefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ1234567890";
35+
3436
extern enum E myConstFunc(struct S* const ctx, int const id, struct S **const filter);
3537
3638
enum E myConstFunc(struct S *const ctx, int const id, struct S **const filter) { return 0; }
@@ -149,6 +151,18 @@ func benchCgoCall(b *testing.B) {
149151
}
150152
}
151153

154+
var sinkString string
155+
156+
func benchGoString(b *testing.B) {
157+
for i := 0; i < b.N; i++ {
158+
sinkString = C.GoString(C.cstr)
159+
}
160+
const want = "abcefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ1234567890"
161+
if sinkString != want {
162+
b.Fatalf("%q != %q", sinkString, want)
163+
}
164+
}
165+
152166
// Issue 2470.
153167
func testUnsignedInt(t *testing.T) {
154168
a := (int64)(C.UINT32VAL)

misc/cgo/test/cgo_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,7 @@ func Test6907(t *testing.T) { test6907(t) }
8787
func Test6907Go(t *testing.T) { test6907Go(t) }
8888
func Test21897(t *testing.T) { test21897(t) }
8989
func Test22906(t *testing.T) { test22906(t) }
90+
func Test24206(t *testing.T) { test24206(t) }
9091

91-
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
92+
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
93+
func BenchmarkGoString(b *testing.B) { benchGoString(b) }

misc/cgo/test/issue24206.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// +build amd64,linux
2+
3+
// Copyright 2018 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package cgotest
8+
9+
// Test that C.GoString uses IndexByte in safe manner.
10+
11+
/*
12+
#include <sys/mman.h>
13+
14+
// Returns string with null byte at the last valid address
15+
char* dangerousString1() {
16+
int pageSize = 4096;
17+
char *data = mmap(0, 2 * pageSize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
18+
mprotect(data + pageSize,pageSize,PROT_NONE);
19+
int start = pageSize - 123 - 1; // last 123 bytes of first page + 1 null byte
20+
int i = start;
21+
for (; i < pageSize; i++) {
22+
data[i] = 'x';
23+
}
24+
data[pageSize -1 ] = 0;
25+
return data+start;
26+
}
27+
28+
char* dangerousString2() {
29+
int pageSize = 4096;
30+
char *data = mmap(0, 3 * pageSize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
31+
mprotect(data + 2 * pageSize,pageSize,PROT_NONE);
32+
int start = pageSize - 123 - 1; // last 123 bytes of first page + 1 null byte
33+
int i = start;
34+
for (; i < 2 * pageSize; i++) {
35+
data[i] = 'x';
36+
}
37+
data[2*pageSize -1 ] = 0;
38+
return data+start;
39+
}
40+
*/
41+
import "C"
42+
43+
import (
44+
"testing"
45+
)
46+
47+
func test24206(t *testing.T) {
48+
if l := len(C.GoString(C.dangerousString1())); l != 123 {
49+
t.Errorf("Incorrect string length - got %d, want 123", l)
50+
}
51+
if l := len(C.GoString(C.dangerousString2())); l != 4096+123 {
52+
t.Errorf("Incorrect string length - got %d, want %d", l, 4096+123)
53+
}
54+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// +build !amd64 !linux
2+
3+
// Copyright 2018 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package cgotest
8+
9+
import "testing"
10+
11+
func test24206(t *testing.T) {
12+
t.Skip("Skipping on non-amd64 or non-linux system")
13+
}

src/runtime/string.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
package runtime
66

7-
import "unsafe"
7+
import (
8+
"internal/bytealg"
9+
"unsafe"
10+
)
811

912
// The constant is known to the compiler.
1013
// There is no fundamental theory behind this number.
@@ -407,12 +410,31 @@ func findnull(s *byte) int {
407410
if s == nil {
408411
return 0
409412
}
410-
p := (*[maxAlloc/2 - 1]byte)(unsafe.Pointer(s))
411-
l := 0
412-
for p[l] != 0 {
413-
l++
413+
414+
// pageSize is the unit we scan at a time looking for NULL.
415+
// It must be the minimum page size for any architecture Go
416+
// runs on. It's okay (just a minor performance loss) if the
417+
// actual system page size is larger than this value.
418+
const pageSize = 4096
419+
420+
offset := 0
421+
ptr := unsafe.Pointer(s)
422+
// IndexByteString uses wide reads, so we need to be careful
423+
// with page boundaries. Call IndexByteString on
424+
// [ptr, endOfPage) interval.
425+
safeLen := int(pageSize - uintptr(ptr)%pageSize)
426+
427+
for {
428+
t := *(*string)(unsafe.Pointer(&stringStruct{ptr, safeLen}))
429+
// Check one page at a time.
430+
if i := bytealg.IndexByteString(t, 0); i != -1 {
431+
return offset + i
432+
}
433+
// Move to next page
434+
ptr = unsafe.Pointer(uintptr(ptr) + uintptr(safeLen))
435+
offset += safeLen
436+
safeLen = pageSize
414437
}
415-
return l
416438
}
417439

418440
func findnullw(s *uint16) int {

0 commit comments

Comments
 (0)