cgo的使用场景
在本章的开头,我们建议大家非必要情况不要用cgo。本小节我们详细来看什么时候用cgo。
场景1——提升算法效率
本例中要求计算512维的向量的欧式距离。我们用go原生实现了算法,然后使用c/c++平台的avx(Advanced Vector Extensions 高级向量拓展集)实现同样的算法,同时比对下效率。
package main
/*
#cgo CFLAGS: -mavx -std=c99
#include <immintrin.h> //AVX: -mavx
float avx_euclidean_distance(const size_t n, float *x, float *y)
{
__m256 vsub,vsum={0},v1,v2;
for(size_t i=0; i < n; i=i+8) {
v1 = _mm256_loadu_ps(x+i);
v2 = _mm256_loadu_ps(y+i);
vsub = _mm256_sub_ps(v1, v2);
vsum = _mm256_add_ps(vsum, _mm256_mul_ps(vsub, vsub));
}
__attribute__((aligned(32))) float t[8] = {0};
_mm256_store_ps(t, vsum);
return t[0] + t[1] + t[2] + t[3] + t[4] + t[5] + t[6] + t[7];
}
*/
import "C"
import (
"fmt"
"time"
)
func euclideanDistance(size int,x, y []float32) float32 { //cgo实现欧式距离
dot := C.avx_euclidean_distance((C.size_t)(size), (*C.float)(&x[0]), (*C.float)(&y[0]))
return float32(dot)
}
func euclidean(infoA, infoB []float32) float32 { //go原生实现欧式距离
var distance float32
for i, number := range infoA {
a := number - infoB[i]
distance += a * a
}
return distance
}
func main() {
size := 512
x := make([]float32, size)
y := make([]float32, size)
for i := 0; i < size; i++ {
x[i] = float32(i)
y[i] = float32(i + 1)
}
stime := time.Now()
times := 1000
for i:=0;i<times;i++ {
euclidean(x,y)
}
fmt.Println(time.Now().Sub(stime))
stime = time.Now()
for i:=0;i<times;i++ {
euclideanDistance(size,x,y)
}
fmt.Println(time.Now().Sub(stime))
}
运行一下
$ go run main.go
447.729µs
143.182µs
cgo实现的比go原生的算法效率大概快了四倍。其实go实现欧式距离算法还可以更快,后续在go汇编会介绍绕过cgo直接使用汇编实现欧式距离计算的方式。
场景2——依赖第三方sdk
在实际开发过程中,我们经常会遇到一些第三方sdk,这些sdk很多为了保护源码会用c和c++编写,然后给你sdk头文件和一个动态或者静态库文件。这个时候只好使用cgo实现自己的业务。
还有就是在c/c++生态产生了很多优秀的项目,比如rocksdb,就是c++实现的本地LSM-Tree实现的内嵌型kv数据库。 很多NewSql的底层数据存储都用rocksdb。所以rocksdb被很多语言集成,有java,python,当然还有go。gorocksdb就是用cgo实现的go rocksdb集成。
总结
本小节介绍了两个使用cgo的场景,这两个场景通常比较常见,淡然除了这两个场景还有一些不常见的场景比如go访问系统驱动,这时候通常也会用cgo实现。总的来说使用cgo需要谨慎些。