【Free Style】CGO: Go与C互操作技术(三):Go调C性能分析及优化
【摘要】 测试环境测试机器为物理机,不是虚拟环境。采用ubuntu 16.04, x64。CPU主频2.6GHz,单核测试。Go的版本为1.8.0。原生Go的调用开销这个是测量的原生Go的开销,作为一个基准,测试代码如下\\ main.gopackage mainimport _ "unsafe"//go:noinlinefunc add3(a, b, c int) int {return a + b +
测试环境
测试机器为物理机,不是虚拟环境。采用ubuntu 16.04, x64。CPU主频2.6GHz,单核测试。Go的版本为1.8.0。
原生Go的调用开销
这个是测量的原生Go的开销,作为一个基准,测试代码如下
\\ main.gopackage mainimport _ "unsafe"//go:noinlinefunc add3(a, b, c int) int {return a + b + c }//go:linkname nanotime runtime.nanotimefunc nanotime() int64 // 如果哪个兄弟想用这个函数,请在目录下建立一个空的asm_amd64.s// 具体原因,在后面的博客中会有介绍// 这个函数与time.Now的区别是没有抢占点,除此之外一模一样const num int = 10000000func main() {var sum int = 0t1 := nanotime()for i := 0; i < num; i ++ { sum = add3(i, i+1, i+2) } t2 := nanotime()println("Time: ", float64(t2-t1)/float64(num), " ns/Op")println("Result:", sum) // println函数是一个builtin的函数,输出到标准错误输出}
输出结果为
time taskset -c 20 ./main Time: +3.270027e+000 ns/Op Result: 30000000
原生Go调C的开销
按照Go的语法使用Go调C
\\ main.gopackage main/* int __attribute__((noinline)) add3(int a, int b, int c) { return a + b + c; } */import "C"const num C.int = 10000000func main() {var sum C.int = 0var i C.intt1 := nanotime()for i = 0; i < num; i++ { sum = C.add3(i, i+1, i+2) } t2 := nanotime()println("Time: ", float64(t2-t1)/float64(num), " ns/Op")println("Result:", sum) }
由于在使用import "C"
的文件中,不能使用编译制导语句,所以需要另外一个文件完成nanotime
的实现
package mainimport _ "unsafe"//go:linkname nanotime runtime.nanotimefunc nanotime() int64
输出结果为:
time taskset -c 20 ./main Time: +8.416507e+001 ns/Op Result: 30000000
通过Go调C花的时间大约是纯Go的26倍。
优化Go调C:去掉调度设置
具体做法的话,就是把C的函数地址拿到,然后通过asmcgocall直接调用。参数的传递通过结构体传递。代码实现如下:
// main.gopackage main/* #include <stdio.h> int __attribute__((noinline)) add3(int a, int b, int c) { return a + b + c; } typedef struct { int p0; int p1; int p2; int r; } passer; int __attribute__((noinline)) x_csub(void *v) { passer* pv = (passer*)v; pv->r = pv->p0 + pv->p1 + pv->p2; } */import "C"const num int32 = 10000000func main() {var sum int32 = 0var i int32t1 := nanotime()for i = 0; i < num; i++ { sum = add3(i, i+1, i+2) } t2 := nanotime()println("Time: ", float64(t2-t1)/float64(num), " ns/Op")println("Result:", sum) }
第二个文件
package mainimport "unsafe"//go:linkname asmcgocall runtime.asmcgocallfunc asmcgocall(fn, arg unsafe.Pointer) int32//go:linkname nanotime runtime.nanotimefunc nanotime() int64//go:cgo_import_static x_csub//go:linkname x_csub x_csub//go:linkname _csub _csubvar x_csub bytevar _csub = unsafe.Pointer(&x_csub)type passer struct { a, b, c, r int32}var p passerfunc add3(a, b, c int32) (r int32) { p.a = a p.b = b p.c = c p.r = r asmcgocall(_csub, unsafe.Pointer(&p))return p.r }
结果输出:
Time: +1.800196e+001 ns/Op Result: 30000000
时间是原生的Go调C的0.21倍。这个结果是仍然有切栈操作的。
优化Go调C:继续去掉切栈操作
继续去掉Go调C的切栈操作,即把一个C函数当成原生的Go函数,去掉切栈操作。代码如下
// main.gopackage main/* #include <stdio.h> int __attribute__((noinline)) add3(int a, int b, int c) { return a + b + c; } typedef struct { int p0; int p1; int p2; int r; } passer; int __attribute__((noinline)) x_csub(void *v) { passer* pv = (passer*)v; pv->r = pv->p0 + pv->p1 + pv->p2; } */import "C"const num int32 = 10000000func main() {var sum int32 = 0var i int32t1 := nanotime()for i = 0; i < num; i++ { sum = add3(i, i+1, i+2) } t2 := nanotime()println("Time: ", float64(t2-t1)/float64(num), " ns/Op")println("Result:", sum) }
与上一个例子类似,另外一个文件为:
// lib.gopackage mainimport "mycall"import "unsafe"//go:linkname asmcgocall runtime.asmcgocallfunc asmcgocall(fn, arg unsafe.Pointer) int32//go:linkname nanotime runtime.nanotimefunc nanotime() int64//go:cgo_import_static x_csub//go:linkname x_csub x_csub//go:linkname _csub _csubvar x_csub bytevar _csub = unsafe.Pointer(&x_csub)type passer struct { a, b, c, r int32}var p passerfunc add3(a, b, c int32) (r int32) { p.a = a p.b = b p.c = c p.r = r mycall.Mycall(_csub, unsafe.Pointer(&p))return p.r }
这里面用到的mycall实现如下:
// mycall.gopackage mycallimport "unsafe"func Mycall(fn, arg unsafe.Pointer)
函数体实现为:
TEXT ·Mycall(SB),0,$0-20MOVQ fn+0(FP), AX MOVQ arg+8(FP), BX MOVQ BX, DI CALL AX RET
输出结果为:
Time: +8.875633e+000 ns/Op Result: 30000000
是原生Go调C的0.105倍。
Go调C优化:把C函数变成Go的汇编
// main.gopackage mainimport _ "unsafe"//go:linkname nanotime runtime.nanotimefunc nanotime() int64func add3(int32, int32, int32) int32const num int32 = 10000000func main() {var sum int32 = 0var i int32t1 := nanotime()for i = 0; i < num; i++ { sum = add3(i, i+1, i+2) } t2 := nanotime()println("Time: ", float64(t2-t1)/float64(num), " ns/Op")println("Result:", sum) }
add3的函数体
// asm_amd64.sTEXT ·add3+0(SB),4,$0-24MOVL c+8(FP),AX ADDL b+4(FP),AX ADDL a+0(FP),AX MOVL AX, ret+16(FP) RET
输出结果为:
Time: +3.390647e+000 ns/Op Result: 30000000
以及跟原生的Go很接近了。
总结
操作 | 时间(ns) | 相对于Go |
---|---|---|
Go | 3.27 | 1.00 |
原生Go C | 84.16 | 25.74 |
去掉调度 | 18.00 | 5.50 |
去掉切栈 | 8.88 | 2.72 |
变成汇编 | 3.39 | 1.04 |
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)