【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)