19

我正在做一个测试:比较 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)

4

3 回答 3

39

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

Go 运行时设置线程的方式有很多方面可以打破许多 C 代码的预期:

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

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

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

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

于 2015-02-02T07:34:41.787 回答
23

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

请参阅golang-nuts 上的此线程:

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

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

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

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

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


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

于 2016-06-22T08:25:47.050 回答
-4

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

于 2015-02-02T07:31:06.607 回答