为什么cgo的性能如此之慢? 我的测试代码有问题吗?

我正在做一个测试:比较cgo和纯Go函数的执行时间各自运行1亿次。 与Golang函数相比,cgo函数需要更长的时间,我对此结果感到困惑。 我的测试代码是:

package main import ( "fmt" "time" ) /* #include  #include  #include  void show() { } */ // #cgo LDFLAGS: -lstdc++ import "C" //import "fmt" func show() { } func main() { now := time.Now() for i := 0; i < 100000000; i = i + 1 { C.show() } end_time := time.Now() var dur_time time.Duration = end_time.Sub(now) var elapsed_min float64 = dur_time.Minutes() var elapsed_sec float64 = dur_time.Seconds() var elapsed_nano int64 = dur_time.Nanoseconds() fmt.Printf("cgo show function elasped %f minutes or \nelapsed %f seconds or \nelapsed %d nanoseconds\n", elapsed_min, elapsed_sec, elapsed_nano) now = time.Now() for i := 0; i < 100000000; i = i + 1 { show() } end_time = time.Now() dur_time = end_time.Sub(now) elapsed_min = dur_time.Minutes() elapsed_sec = dur_time.Seconds() elapsed_nano = dur_time.Nanoseconds() fmt.Printf("go show function elasped %f minutes or \nelapsed %f seconds or \nelapsed %d nanoseconds\n", elapsed_min, elapsed_sec, elapsed_nano) var input string fmt.Scanln(&input) } 

结果是:

 cgo show function elasped 0.368096 minutes or elapsed 22.085756 seconds or elapsed 22085755775 nanoseconds go show function elasped 0.000654 minutes or elapsed 0.039257 seconds or elapsed 39257120 nanoseconds 

结果表明,调用C函数比Go函数慢。 我的测试代码有问题吗?

我的系统是:mac OS X 10.9.4(13E28)

正如您所发现的,通过CGo调用C / C ++代码的开销相当高。 所以一般来说,你最好尽量减少你所做的CGo通话次数。 对于上面的示例,不是在循环中重复调用CGo函数,而是将循环向下移动到C可能是有意义的。

Go运行时如何设置其线程有许多方面可以打破许多C代码的期望:

  1. Goroutines运行在相对较小的堆栈上,通过分段堆栈(旧版本)或通过复制(新版本)处理堆栈增长。
  2. Go运行时创建的线程可能无法与libpthread的线程本地存储实现正确交互。
  3. Go运行时的UNIX信号处理程序可能会干扰传统的C或C ++代码。
  4. Go重用OS线程来运行多个Goroutines。 如果C代码调用阻塞系统调用或以其他方式垄断线程,则可能对其他goroutine有害。

出于这些原因,CGo选择了在使用传统堆栈设置的单独线程中运行C代码的安全方法。

如果你来自像Python这样的语言,在C语言中重写代码热点作为加速程序的一种方式并不罕见,你会感到失望。 但与此同时,等效C和Go代码之间的性能差距要小得多。

一般来说,我保留CGo用于连接现有的库,可能还有小的C包装函数,可以减少我需要从Go调用的次数。

James的回答更新:似乎当前实现中没有线程切换。

在golang-nuts上看到这个post :

总会有一些开销。 它比简单的函数调用更昂贵,但比上下文切换要便宜得多(agl记住了早期的实现; 我们在公开发布之前切断了线程切换 )。 现在,费用基本上只需要进行完整的寄存器设置切换(没有内核参与)。 我猜它可以与十个函数调用相媲美。

另请参阅此答案链接“cgo is not Go”博客文章。

C对Go的调用约定或可增长的堆栈一无所知,因此调用C代码必须记录goroutine堆栈的所有细节,切换到C堆栈,并运行不知道如何调用它的C代码,或负责程序的较大的Go运行时。

因此,cgo有一个开销,因为它执行堆栈切换 ,而不是线程切换。

它在调用C函数时保存并恢复所有寄存器,而在调用Go函数或汇编函数时则不需要它。


除此之外,cgo的调用约定禁止将Go指针直接传递给C代码,常见的解决方法是使用C.malloc ,因此引入了额外的分配。 有关详细信息,请参阅此问

从Go调用C函数有一点开销。 这不能改变。