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

时间:2015-02-02 06:35:13

标签: c performance go cgo

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

package main

import (
    "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 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)

3 个答案:

答案 0 :(得分:31)

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

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

  1. Goroutines运行在相对较小的堆栈上,通过分段堆栈(旧版本)或通过复制(新版本)处理堆栈增长。
  2. Go运行时创建的线程可能无法与libpthread的线程本地存储实现正常交互。
  3. Go运行时的UNIX信号处理程序可能会干扰传统的C或C ++代码。
  4. Go重用OS线程来运行多个Goroutines。如果C代码调用阻塞系统调用或以其他方式垄断线程,则可能对其他goroutines有害。
  5. 由于这些原因,CGo选择了在使用传统堆栈设置的单独线程中运行C代码的安全方法。

    如果您来自Python这样的语言,在C语言中重写代码热点并不常见,以加快程序的速度,您会感到失望。但与此同时,等效C和Go代码之间的性能差距要小得多。

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

答案 1 :(得分:14)

詹姆斯answer的更新:似乎当前的实施中没有线程切换。

请参阅关于golang-nuts的this thread

  

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

另请参阅this answer链接"cgo is not Go"博文。

  

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

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

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

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

答案 2 :(得分:-4)

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