为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
largeQ 2021-09-13 15:13:53
我在做一个测试:比较 cgo 和纯 Go 函数的执行时间,分别运行 1 亿次。与 Golang 函数相比,cgo 函数需要更长的时间,我对这个结果感到困惑。我的测试代码是:package mainimport (    "fmt"    "time")/*#include <stdio.h>#include <stdlib.h>#include <string.h>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 nanosecondsgo show function elasped 0.000654 minutes or elapsed 0.039257 seconds or elapsed 39257120 nanoseconds结果表明,调用 C 函数比 Go 函数慢。我的测试代码有问题吗?我的系统是:mac OS X 10.9.4 (13E28)
查看完整描述

3 回答

?
繁星点点滴滴

TA贡献1803条经验 获得超3个赞

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

Go 运行时如何设置其线程有很多方面可能会打破许多 C 代码片段的预期:

  1. Goroutines 运行在一个相对较小的堆栈上,通过分段堆栈(旧版本)或通过复制(新版本)处理堆栈增长。

  2. Go 运行时创建的线程可能无法与libpthread的线程本地存储实现正确交互。

  3. Go 运行时的 UNIX 信号处理程序可能会干扰传统的 C 或 C++ 代码。

  4. Go 重用 OS 线程来运行多个 Goroutine。如果 C 代码调用阻塞系统调用或以其他方式独占线程,则可能对其他 goroutine 有害。

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

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

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


查看完整回答
反对 回复 2021-09-13
?
慕的地6264312

TA贡献1817条经验 获得超6个赞

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

在 golang-nuts 上看到这个线程:

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

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

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

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

调用C函数时保存和恢复所有寄存器,调用Go函数或汇编函数时不需要。


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


查看完整回答
反对 回复 2021-09-13
?
回首忆惘然

TA贡献1847条经验 获得超11个赞

从 Go 调用 C 函数有一点开销。这是无法改变的。


查看完整回答
反对 回复 2021-09-13
  • 3 回答
  • 0 关注
  • 296 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信