mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/cgo: optimize cgoCheckPointer call
Currently cgoCheckPointer is only used with one optional argument.
Using a slice for the optional arguments is quite expensive, hence
replace it with a single interface{}. This results in ~30% improvement.
When checking struct fields, they quite often end up being without
pointers. Check this before calling cgoCheckPointer, which results in
additional ~20% improvement.
Inline some p == nil checks from cgoIsGoPointer which gives
additional ~15% improvement.
All of this translates to:
name old time/op new time/op delta
CgoCall/add-int-32 46.9ns ± 1% 46.6ns ± 1% -0.75% (p=0.000 n=18+20)
CgoCall/one-pointer-32 143ns ± 1% 87ns ± 1% -38.96% (p=0.000 n=20+20)
CgoCall/eight-pointers-32 767ns ± 0% 327ns ± 1% -57.30% (p=0.000 n=18+16)
CgoCall/eight-pointers-nil-32 110ns ± 1% 89ns ± 2% -19.10% (p=0.000 n=19+19)
CgoCall/eight-pointers-array-32 5.09µs ± 1% 3.56µs ± 2% -30.09% (p=0.000 n=19+19)
CgoCall/eight-pointers-slice-32 3.92µs ± 0% 2.57µs ± 2% -34.48% (p=0.000 n=20+20)
Change-Id: I2aa9f5ae8962a9a41a7fb1db0c300893109d0d75
Reviewed-on: https://go-review.googlesource.com/c/go/+/198081
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
30d7b64008
commit
e85ffec784
4 changed files with 98 additions and 20 deletions
|
|
@ -115,6 +115,44 @@ int add(int x, int y) {
|
||||||
return x+y;
|
return x+y;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Following mimicks vulkan complex definitions for benchmarking cgocheck overhead.
|
||||||
|
|
||||||
|
typedef uint32_t VkFlags;
|
||||||
|
typedef VkFlags VkDeviceQueueCreateFlags;
|
||||||
|
typedef uint32_t VkStructureType;
|
||||||
|
|
||||||
|
typedef struct VkDeviceQueueCreateInfo {
|
||||||
|
VkStructureType sType;
|
||||||
|
const void* pNext;
|
||||||
|
VkDeviceQueueCreateFlags flags;
|
||||||
|
uint32_t queueFamilyIndex;
|
||||||
|
uint32_t queueCount;
|
||||||
|
const float* pQueuePriorities;
|
||||||
|
} VkDeviceQueueCreateInfo;
|
||||||
|
|
||||||
|
typedef struct VkPhysicalDeviceFeatures {
|
||||||
|
uint32_t bools[56];
|
||||||
|
} VkPhysicalDeviceFeatures;
|
||||||
|
|
||||||
|
typedef struct VkDeviceCreateInfo {
|
||||||
|
VkStructureType sType;
|
||||||
|
const void* pNext;
|
||||||
|
VkFlags flags;
|
||||||
|
uint32_t queueCreateInfoCount;
|
||||||
|
const VkDeviceQueueCreateInfo* pQueueCreateInfos;
|
||||||
|
uint32_t enabledLayerCount;
|
||||||
|
const char* const* ppEnabledLayerNames;
|
||||||
|
uint32_t enabledExtensionCount;
|
||||||
|
const char* const* ppEnabledExtensionNames;
|
||||||
|
const VkPhysicalDeviceFeatures* pEnabledFeatures;
|
||||||
|
} VkDeviceCreateInfo;
|
||||||
|
|
||||||
|
void handleComplexPointer(VkDeviceCreateInfo *a0) {}
|
||||||
|
void handleComplexPointer8(
|
||||||
|
VkDeviceCreateInfo *a0, VkDeviceCreateInfo *a1, VkDeviceCreateInfo *a2, VkDeviceCreateInfo *a3,
|
||||||
|
VkDeviceCreateInfo *a4, VkDeviceCreateInfo *a5, VkDeviceCreateInfo *a6, VkDeviceCreateInfo *a7
|
||||||
|
) {}
|
||||||
|
|
||||||
// complex alignment
|
// complex alignment
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -993,11 +1031,45 @@ type Context struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchCgoCall(b *testing.B) {
|
func benchCgoCall(b *testing.B) {
|
||||||
|
b.Run("add-int", func(b *testing.B) {
|
||||||
const x = C.int(2)
|
const x = C.int(2)
|
||||||
const y = C.int(3)
|
const y = C.int(3)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
C.add(x, y)
|
C.add(x, y)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("one-pointer", func(b *testing.B) {
|
||||||
|
var a0 C.VkDeviceCreateInfo
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
C.handleComplexPointer(&a0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("eight-pointers", func(b *testing.B) {
|
||||||
|
var a0, a1, a2, a3, a4, a5, a6, a7 C.VkDeviceCreateInfo
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
C.handleComplexPointer8(&a0, &a1, &a2, &a3, &a4, &a5, &a6, &a7)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("eight-pointers-nil", func(b *testing.B) {
|
||||||
|
var a0, a1, a2, a3, a4, a5, a6, a7 *C.VkDeviceCreateInfo
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
C.handleComplexPointer8(a0, a1, a2, a3, a4, a5, a6, a7)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("eight-pointers-array", func(b *testing.B) {
|
||||||
|
var a [8]C.VkDeviceCreateInfo
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
C.handleComplexPointer8(&a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("eight-pointers-slice", func(b *testing.B) {
|
||||||
|
a := make([]C.VkDeviceCreateInfo, 8)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
C.handleComplexPointer8(&a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7])
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark measuring overhead from Go to C and back to Go (via a callback)
|
// Benchmark measuring overhead from Go to C and back to Go (via a callback)
|
||||||
|
|
|
||||||
|
|
@ -816,7 +816,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) {
|
||||||
// Rewrite C.f(p) to
|
// Rewrite C.f(p) to
|
||||||
// func() {
|
// func() {
|
||||||
// _cgo0 := p
|
// _cgo0 := p
|
||||||
// _cgoCheckPointer(_cgo0)
|
// _cgoCheckPointer(_cgo0, nil)
|
||||||
// C.f(_cgo0)
|
// C.f(_cgo0)
|
||||||
// }()
|
// }()
|
||||||
// Using a function literal like this lets us evaluate the
|
// Using a function literal like this lets us evaluate the
|
||||||
|
|
@ -834,7 +834,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) {
|
||||||
// defer func() func() {
|
// defer func() func() {
|
||||||
// _cgo0 := p
|
// _cgo0 := p
|
||||||
// return func() {
|
// return func() {
|
||||||
// _cgoCheckPointer(_cgo0)
|
// _cgoCheckPointer(_cgo0, nil)
|
||||||
// C.f(_cgo0)
|
// C.f(_cgo0)
|
||||||
// }
|
// }
|
||||||
// }()()
|
// }()()
|
||||||
|
|
@ -921,7 +921,7 @@ func (p *Package) rewriteCall(f *File, call *Call) (string, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&sb, "_cgo%d := %s; ", i, gofmtPos(arg, origArg.Pos()))
|
fmt.Fprintf(&sb, "_cgo%d := %s; ", i, gofmtPos(arg, origArg.Pos()))
|
||||||
fmt.Fprintf(&sbCheck, "_cgoCheckPointer(_cgo%d); ", i)
|
fmt.Fprintf(&sbCheck, "_cgoCheckPointer(_cgo%d, nil); ", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if call.Deferred {
|
if call.Deferred {
|
||||||
|
|
|
||||||
|
|
@ -1631,14 +1631,14 @@ func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
|
||||||
func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)
|
func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)
|
||||||
|
|
||||||
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
|
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
|
||||||
func _cgoCheckPointer(interface{}, ...interface{})
|
func _cgoCheckPointer(interface{}, interface{})
|
||||||
|
|
||||||
//go:linkname _cgoCheckResult runtime.cgoCheckResult
|
//go:linkname _cgoCheckResult runtime.cgoCheckResult
|
||||||
func _cgoCheckResult(interface{})
|
func _cgoCheckResult(interface{})
|
||||||
`
|
`
|
||||||
|
|
||||||
const gccgoGoProlog = `
|
const gccgoGoProlog = `
|
||||||
func _cgoCheckPointer(interface{}, ...interface{})
|
func _cgoCheckPointer(interface{}, interface{})
|
||||||
|
|
||||||
func _cgoCheckResult(interface{})
|
func _cgoCheckResult(interface{})
|
||||||
`
|
`
|
||||||
|
|
@ -1825,16 +1825,16 @@ typedef struct __go_empty_interface {
|
||||||
void *__object;
|
void *__object;
|
||||||
} Eface;
|
} Eface;
|
||||||
|
|
||||||
extern void runtimeCgoCheckPointer(Eface, Slice)
|
extern void runtimeCgoCheckPointer(Eface, Eface)
|
||||||
__asm__("runtime.cgoCheckPointer")
|
__asm__("runtime.cgoCheckPointer")
|
||||||
__attribute__((weak));
|
__attribute__((weak));
|
||||||
|
|
||||||
extern void localCgoCheckPointer(Eface, Slice)
|
extern void localCgoCheckPointer(Eface, Eface)
|
||||||
__asm__("GCCGOSYMBOLPREF._cgoCheckPointer");
|
__asm__("GCCGOSYMBOLPREF._cgoCheckPointer");
|
||||||
|
|
||||||
void localCgoCheckPointer(Eface ptr, Slice args) {
|
void localCgoCheckPointer(Eface ptr, Eface arg) {
|
||||||
if(runtimeCgoCheckPointer) {
|
if(runtimeCgoCheckPointer) {
|
||||||
runtimeCgoCheckPointer(ptr, args);
|
runtimeCgoCheckPointer(ptr, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -406,7 +406,7 @@ var racecgosync uint64 // represents possible synchronization in C code
|
||||||
|
|
||||||
// cgoCheckPointer checks if the argument contains a Go pointer that
|
// cgoCheckPointer checks if the argument contains a Go pointer that
|
||||||
// points to a Go pointer, and panics if it does.
|
// points to a Go pointer, and panics if it does.
|
||||||
func cgoCheckPointer(ptr interface{}, args ...interface{}) {
|
func cgoCheckPointer(ptr interface{}, arg interface{}) {
|
||||||
if debug.cgocheck == 0 {
|
if debug.cgocheck == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -415,15 +415,15 @@ func cgoCheckPointer(ptr interface{}, args ...interface{}) {
|
||||||
t := ep._type
|
t := ep._type
|
||||||
|
|
||||||
top := true
|
top := true
|
||||||
if len(args) > 0 && (t.kind&kindMask == kindPtr || t.kind&kindMask == kindUnsafePointer) {
|
if arg != nil && (t.kind&kindMask == kindPtr || t.kind&kindMask == kindUnsafePointer) {
|
||||||
p := ep.data
|
p := ep.data
|
||||||
if t.kind&kindDirectIface == 0 {
|
if t.kind&kindDirectIface == 0 {
|
||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
}
|
}
|
||||||
if !cgoIsGoPointer(p) {
|
if p == nil || !cgoIsGoPointer(p) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aep := (*eface)(unsafe.Pointer(&args[0]))
|
aep := (*eface)(unsafe.Pointer(&arg))
|
||||||
switch aep._type.kind & kindMask {
|
switch aep._type.kind & kindMask {
|
||||||
case kindBool:
|
case kindBool:
|
||||||
if t.kind&kindMask == kindUnsafePointer {
|
if t.kind&kindMask == kindUnsafePointer {
|
||||||
|
|
@ -460,7 +460,7 @@ const cgoResultFail = "cgo result has Go pointer"
|
||||||
// depending on indir. The top parameter is whether we are at the top
|
// depending on indir. The top parameter is whether we are at the top
|
||||||
// level, where Go pointers are allowed.
|
// level, where Go pointers are allowed.
|
||||||
func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
|
func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
|
||||||
if t.ptrdata == 0 {
|
if t.ptrdata == 0 || p == nil {
|
||||||
// If the type has no pointers there is nothing to do.
|
// If the type has no pointers there is nothing to do.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -517,7 +517,7 @@ func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
|
||||||
st := (*slicetype)(unsafe.Pointer(t))
|
st := (*slicetype)(unsafe.Pointer(t))
|
||||||
s := (*slice)(p)
|
s := (*slice)(p)
|
||||||
p = s.array
|
p = s.array
|
||||||
if !cgoIsGoPointer(p) {
|
if p == nil || !cgoIsGoPointer(p) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !top {
|
if !top {
|
||||||
|
|
@ -548,11 +548,17 @@ func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, f := range st.fields {
|
for _, f := range st.fields {
|
||||||
|
if f.typ.ptrdata == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
cgoCheckArg(f.typ, add(p, f.offset()), true, top, msg)
|
cgoCheckArg(f.typ, add(p, f.offset()), true, top, msg)
|
||||||
}
|
}
|
||||||
case kindPtr, kindUnsafePointer:
|
case kindPtr, kindUnsafePointer:
|
||||||
if indir {
|
if indir {
|
||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
|
if p == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cgoIsGoPointer(p) {
|
if !cgoIsGoPointer(p) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue