LoginSignup
41
40

More than 5 years have passed since last update.

cgoがGoのコンパイル速度に与える影響

Last updated at Posted at 2015-01-06

はじめに

(タイトルはScala の機能がコンパイル速度に与える影響にインスパイアされました。)

Goのコンパイル速度の速さはセールスポイントの一つです。しかし、そんなGoもcgoを使い始めるとコンパイルが途端に遅くなります。あんまりこのあたりについて言及しているエントリがないので書いてみることにしました。

このエントリではサンプルプログラムがいっぱい出てくるので以下のリポジトリにまとめました。

また、コンパイル速度の比較もmakeで行えるようになっています。

cgo

cgoはGoからCのコードを実行するための仕組みです。例えばこんな風にGoからCの関数を呼び出すことができます。

helloworld.go
package main

/*
#include <stdio.h>
void helloworld() {
    printf("Hello, World!\n");
}
*/
import "C"

func main() {
        C.helloworld()
}

*/import "C"の間に空行があるとコンパイルエラーになるので注意しましょう。(地味にハマリポイント)

$ go run helloworld.go
Hello, World!
$

ピュアGoのプログラム

例えば以下のような全部Goで書かれたプログラムがあるとします。

go
├── add.go
├── div.go
├── main.go
├── mul.go
└── sub.go
go/main.go
package main

import (
        "fmt"
)

func main() {
        fmt.Println(add(5, 2))
        fmt.Println(sub(5, 2))
        fmt.Println(mul(5, 2))
        fmt.Println(div(5, 2))
}
go/add.go
package main

func add(a, b int) int {
        return a + b
}
go/sub.go
package main

func sub(a, b int) int {
        return a - b
}
go/mul.go
package main

func mul(a, b int) int {
        return a * b
}
go/div.go
package main

func div(a, b int) int {
        return a / b
}

これをビルドしてかかった時間を計測します。

$ cd go
$ time go build
go build  0.13s user 0.04s system 98% cpu 0.164 total
$

cgoを利用したGoプログラム

続いてさきほどのGoプログラムをcgo化したのが以下になります。(main.goはさっきと同じです)

cgo
├── add.go
├── div.go
├── main.go
├── mul.go
└── sub.go
cgo/add.go
package main

/*
int add(int a, int b) {
    return a + b;
}
*/
import "C"

func add(a, b int) int {
        return int(C.add(C.int(a), C.int(b)))
}
cgo/sub.go
package main

/*
int sub(int a, int b) {
    return a - b;
}
*/
import "C"

func sub(a, b int) int {
        return int(C.sub(C.int(a), C.int(b)))
}
cgo/mul.go
package main

/*
int mul(int a, int b) {
    return a * b;
}
*/
import "C"

func mul(a, b int) int {
        return int(C.mul(C.int(a), C.int(b)))
}
cgo/div.go
package main

/*
int div(int a, int b) {
    return a / b;
}
*/
import "C"

func div(a, b int) int {
        return int(C.div(C.int(a), C.int(b)))
}

これをビルドします。

$ cd cgo
$ time go build
go build  0.31s user 0.25s system 96% cpu 0.583 total
$

ピュアGoのプログラムに比べてコンパイルするのに3倍以上の時間がかかるようになりました。

cgoを利用したGoプログラムのコンパイルを速くする

次にcgoを利用したGoプログラムのコンパイルを速くしてみます。さっきまでと違って各演算の関数を一つのファイル(calc.go)にまとめているのがポイントです。(このmain.goもこれまでのと同じです)

cgofast
├── calc.go
└── main.go
cgofast/calc.go
package main

/*
int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int mul(int a, int b) {
    return a * b;
}

int div(int a, int b) {
    return a / b;
}
*/
import "C"

func add(a, b int) int {
        return int(C.add(C.int(a), C.int(b)))
}

func sub(a, b int) int {
        return int(C.sub(C.int(a), C.int(b)))
}

func mul(a, b int) int {
        return int(C.mul(C.int(a), C.int(b)))
}

func div(a, b int) int {
        return int(C.div(C.int(a), C.int(b)))
}

これをビルドしてみます。

$ cd cgofast
$ time go build
go build  0.23s user 0.13s system 95% cpu 0.367 total
$

さきほどよりも少しだけ速くなりました。

GoとCとcgo

このことからわかるのはcgoを利用したソースファイル(.go)が多ければ多いほどGoのコンパイル(go build)は遅くなるということです。感覚的にはcgoを利用した1ソースファイルにつき数百msecといったところでしょうか。

go buildはCのコンパイルと違ってソースファイル毎にオブジェクトファイルを生成して差分ビルドみたいなことはしないので、おそらくgo buildの度に毎回cgoの部分のコンパイルをやってるのがcgoを利用したプログラムのgo buildが遅くなる原因ではないかと思います。

cgofastのビルドが速いのはcgoの部分を1つのファイルにまとめたことで、Cのコンパイルが走る回数が減ったためと思われます。

(追記@mattnさんからパッケージにして事前にgo installするともっと速くなるよというコメントを頂きました)

今回の例ではプログラムが非常に小さいくて実感が湧かないかも知れませんが、昔担当していたそれなりに規模のあるGoのサーバプロダクトでは深淵な理由からcgoを多用していたこともあってコンパイルするのに10秒以上かかっていました。(開発機が非力なMacBook Airだったり、プロプライエタリなライブラリを利用する関係でLinuxで開発するためにVagrant上でコンパイルしなければならなかったりしたのもあるけど)

41
40
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
40