19

私はテストを行っています: cgo と純粋な Go 関数の実行時間をそれぞれ 1 億回実行して比較します。cgo 関数は Golang 関数に比べて時間がかかり、この結果に混乱しています。私のテストコードは次のとおりです。

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. ゴルーチンは比較的小さなスタックで実行され、セグメント化されたスタック (古いバージョン) またはコピー (新しいバージョン) によってスタックの増加を処理します。
  2. libpthreadGo ランタイムによって作成されたスレッドは、のスレッド ローカル ストレージの実装と適切に対話しない場合があります。
  3. Go ランタイムの UNIX シグナル ハンドラは、従来の C または C++ コードに干渉する可能性があります。
  4. Go は OS スレッドを再利用して複数の Goroutine を実行します。C コードがブロッキング システム コールを呼び出したり、スレッドを独占したりすると、他のゴルーチンに悪影響を与える可能性があります。

これらの理由から、CGo は、従来のスタックを使用して設定された別のスレッドで C コードを実行するという安全なアプローチを採用しています。

プログラムを高速化する方法としてコードのホットスポットを C で書き直すことが珍しくない Python のような言語から来ている場合は、がっかりするでしょう。しかし同時に、同等の C コードと Go コードの間のパフォーマンスの差ははるかに小さくなっています。

一般に、私は既存のライブラリとのインターフェース用に CGo を予約しています。おそらく、Go から行う必要がある呼び出しの数を減らすことができる小さな C ラッパー関数を使用します。

于 2015-02-02T07:34:41.787 に答える
23

ジェームズの答えの更新:現在の実装にはスレッドスイッチがないようです。

golang-nuts に関するこのスレッドを参照してください。

常にいくらかのオーバーヘッドが発生します。これは、単純な関数呼び出しよりもコストがかかりますが、コンテキスト スイッチよりも大幅に安価です (agl は以前の実装を覚えています 。公開リリース前にスレッド スイッチを削除しました)。現在のところ、費用は基本的に完全なレジスタセットの切り替えを行うだけです (カーネルの関与はありません)。10回の関数呼び出しに匹敵すると思います。

「cgo is not Go」ブログ投稿にリンクしているこの回答も参照してください。

C は Go の呼び出し規則や成長可能なスタックについて何も知らないため、C コードへの呼び出しでは、ゴルーチン スタックのすべての詳細を記録し、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 に答える