Open8

goでmoldを試す

nasanasa

go言語のリンクは外部のリンカで行われる場合と内部リンカで行われる場合があるらしい。

内部リンカはgoで書かれているもので、外部リンカより高速なので出来るだけコチラを使うみたい。
でもcgoが絡んでくるとリンクできないのでこのときは外部リンカを使うらしい。

詳しい条件は知らない。

内部リンカは高速だと言っているがmoldと比べたらどうなんだろう?と気になったので試してみた。
ついでに他のリンカとの速度比較も行ってみた。

nasanasa

外部リンカを呼ぶ方法

go buildコマンドにはldflagsオプションがある。このオプションを使用してリンカ内部リンカ・外部リンカのどちらを使用するか決められる。

go build -ldflags="-linkmode=internal" main.go # 内部リンカ
go build -ldflags="-linkmode=external" main.go # 外部リンカ

リンカのオプションはgo tool link -hで確認できる。

リンカオプションextldを使うと任意のリンカを呼べそうな雰囲気がある。

ドキュメントによると外部リンカのデフォルト値はgccかclangになるみたい。

どうやらコンパイラフロントエンド(?)を経由してリンカを呼び出しているみたい。

なので以下のように外部リンカに渡すオプションextldflagsを指定してgccに目的のリンカを呼んでもらうことにした。

go build -ldflags="-linkmode=external -extldflags=-fuse-ld=lld -extld=gcc" main.go

go build → go tool link → gcc → 任意のリンカというようにいくつも経由して呼び出している。

nasanasa

任意のリンカを呼べる所まで来たので計測してみよう。

とりあえずhello worldで計測した。
hello worldだけだとリンカの仕事が少なすぎるので大きめのコードjujudをビルドしてみた。

実験環境

  • Amazon ec2の c4.largeインスタンスを使用
  • CPU 2コア
  • memory 3.75GiB

計測方法

コンパイル時間を除去するために一度コンパイルし、実行ファイルを削除することでリンカのみを動かすようにした。(リンカ以外も動いているが実行ログを見るに対して時間がかかってなかったので無視する)

計測に使用したコード
package main

import (
	"fmt"
	"log"
	"os/exec"
	"time"
)

// Dependencies: go_build.sh
// ```go_build.sh
// #!/bin/bash
// set -eux
// linkmode=$1
// ld=$2
// touch ./tmp/jujud && rm ./tmp/jujud && time go build -ldflags="-linkmode=$linkmode -extldflags=-fuse-ld=$ld -extld=gcc" -o ./tmp/jujud github.com/juju/juju/cmd/jujud
// ```

func main() {
	experiments := [][]string{
		{"internal", "gold"}, // dummy linker
		{"external", "gold"},
		{"external", "lld"},
		{"external", "mold"},
	}

	num := 10

	results := map[string]time.Duration{}

	for _, experiment := range experiments {
		for i := 0; i < num; i++ {
			start := time.Now()
			if err := exec.Command("./go_build.sh", experiment[0], experiment[1]).Run(); err != nil {
				panic(err)
			}
			log.Printf("experiment %v, iteration %v, duration %v", experiment, i, time.Since(start))
			results[experiment[0]+" "+experiment[1]] += time.Since(start)
		}
	}

	for k, v := range results {
		fmt.Printf("%s: %s (%.2f)\n", k, v, float64(v)/float64(num))
	}
}

結果

実験コード internal linker gold lld mold
hello world 179ms 274ms 370ms 271ms
jujud 11.64s 17.39s 14.27s 14.90s
nasanasa

感想。

gccの仕事がわからないので今回計測できたのは gcc + リンカの実行時間になっている。

外部リンカを呼び出すのにもコストがかかるのでそれで時間がかかっているのかもしれない。

まあとりあえず内部リンカが一番早いのでこれを使っておけば良いみたい。

なんで速いのかは後日調べてみる。

nasanasa

cpuprofileオプションでリンカの内訳を調べる

go build -ldflags="-cpuprofile=path -linkmode=external -extldflags=-fuse-ld=mold -extld=gcc" main.go
go build -ldflags="-cpuprofile=path -linkmode=internal" main.go

go tool pprof -text -cum internal_prof
go tool pprof -text -cum external_prof

内部・外部で差分はどこにあるのか

comm -13 internal_functions external_functions

でexternal linkerでのみ実行されている関数を探してみる。

~/lab/sandbox/go_linker_profile
:) % rg -f pat logs/external_profile
14:     0.05s  0.76%  1.21%      0.99s 15.00%  cmd/link/internal/ld.elfrelocsect
15:         0     0%  1.21%      0.99s 15.00%  cmd/link/internal/ld.relocSectFn.func1.1
22:     0.02s   0.3% 14.85%      0.46s  6.97%  cmd/link/internal/amd64.elfreloc1
35:     0.19s  2.88% 24.24%      0.34s  5.15%  runtime.mapaccess1_fast32
36:     0.01s  0.15% 24.39%      0.33s  5.00%  cmd/link/internal/ld.ElfSymForReloc (inline)
68:     0.04s  0.61% 46.36%      0.19s  2.88%  cmd/link/internal/ld.extreloc
69:         0     0% 46.36%      0.19s  2.88%  runtime.newobject (partial-inline)
92:     0.12s  1.82% 59.70%      0.12s  1.82%  runtime/internal/syscall.Syscall6
95:     0.11s  1.67% 61.36%      0.11s  1.67%  runtime.memhash32
96:     0.02s   0.3% 61.67%      0.10s  1.52%  cmd/internal/dwarf.Uleb128put

100ms以上ので絞るとこんな感じ。
これらはexternal linkerでのみ呼ばれているし合計3,4秒時間を食っている。
差分としてはこれが有力に見える。(3,4秒と言ってもCPU時間であり現実時間じゃない)

nasanasa

リンカのログを読む

ちゃんと調べるとリンカにも詳細ログを出力するオプションがあった。(ソースコードを読んでて気づいた、、、interfaceをちゃんと調べようねとなった)

-vで外部リンカのコマンドが分かる。そして-tmpdirに中間結果を残すことが出来る。

これらを指定すると外部リンカのみ呼び出せる

 go build -ldflags="-linkmode=external -extldflags=-fuse-ld=lld -extld=gcc -v -tmpdir=path" main.go

ログを見るとgccのコマンドが分かる

host link: "gcc" "-m64" "-Wl,-z,now" "-Wl,-z,nocopyreloc" "-o" "/tmp/go-build4004567054/b001/exe/a.out" "-rdynamic" "-Wl,--compress-debug-sections=zlib" "/tmp/go-link-1959394836/go.o" "/tmp/go-link-1959394836/000000.o" "/tmp/go-link-1959394836/000001.o" "/tmp/go-link-1959394836/000002.o" "/tmp/go-link-1959394836/000003.o" "/tmp/go-link-1959394836/000004.o" "/tmp/go-link-1959394836/000005.o" "/tmp/go-link-1959394836/000006.o" "/tmp/go-link-1959394836/000007.o" "/tmp/go-link-1959394836/000008.o" "/tmp/go-link-1959394836/000009.o" "/tmp/go-link-1959394836/000010.o" "/tmp/go-link-1959394836/000011.o" "/tmp/go-link-1959394836/000012.o" "/tmp/go-link-1959394836/000013.o" "/tmp/go-link-1959394836/000014.o" "/tmp/go-link-1959394836/000015.o" "/tmp/go-link-1959394836/000016.o" "/tmp/go-link-1959394836/000017.o" "/tmp/go-link-1959394836/000018.o" "/tmp/go-link-1959394836/000019.o" "/tmp/go-link-1959394836/000020.o" "/tmp/go-link-1959394836/000021.o" "/tmp/go-link-1959394836/000022.o" "/tmp/go-link-1959394836/000023.o" "/tmp/go-link-1959394836/000024.o" "/tmp/go-link-1959394836/000025.o" "/tmp/go-link-1959394836/000026.o" "/tmp/go-link-1959394836/000027.o" "/tmp/go-link-1959394836/000028.o" "/tmp/go-link-1959394836/000029.o" "/tmp/go-link-1959394836/000030.o" "/tmp/go-link-1959394836/000031.o" "/tmp/go-link-1959394836/000032.o" "/tmp/go-link-1959394836/000033.o" "/tmp/go-link-1959394836/000034.o" "/tmp/go-link-1959394836/000035.o" "/tmp/go-link-1959394836/000036.o" "/tmp/go-link-1959394836/000037.o" "/tmp/go-link-1959394836/000038.o" "/tmp/go-link-1959394836/000039.o" "/tmp/go-link-1959394836/000040.o" "/tmp/go-link-1959394836/000041.o" "/tmp/go-link-1959394836/000042.o" "/tmp/go-link-1959394836/000043.o" "/tmp/go-link-1959394836/000044.o" "/tmp/go-link-1959394836/000045.o" "/tmp/go-link-1959394836/000046.o" "/tmp/go-link-1959394836/000047.o" "/tmp/go-link-1959394836/000048.o" "/tmp/go-link-1959394836/000049.o" "/tmp/go-link-1959394836/000050.o" "/tmp/go-link-1959394836/000051.o" "/tmp/go-link-1959394836/000052.o" "/tmp/go-link-1959394836/000053.o" "/tmp/go-link-1959394836/000054.o" "/tmp/go-link-1959394836/000055.o" "/tmp/go-link-1959394836/000056.o" "/tmp/go-link-1959394836/000057.o" "/tmp/go-link-1959394836/000058.o" "/tmp/go-link-1959394836/000059.o" "/tmp/go-link-1959394836/000060.o" "/tmp/go-link-1959394836/000061.o" "/tmp/go-link-1959394836/000062.o" "/tmp/go-link-1959394836/000063.o" "/tmp/go-link-1959394836/000064.o" "/tmp/go-link-1959394836/000065.o" "/tmp/go-link-1959394836/000066.o" "-O2" "-g" "-O2" "-g" "-lresolv" "-O2" "-g" "-lpthread" "-O2" "-g" "-ldl" "-O2" "-g" "-no-pie" "-fuse-ld=lld"

無事呼び出せた。lldのがmoldより速い

ubuntu@ip-172-31-47-209:~/lab/k9s$ time gcc -m64 -Wl,-z,now -Wl,-z,nocopyreloc -o /tmp/go-build1945961237/b001/exe/a.out -rdynamic -Wl,--compress-debug-sections=zlib /home/ubuntu/lab/k9s/tmp/go.o /home/ubuntu/lab/k9s/tmp/000000.o /home/ubuntu/lab/k9s/tmp/000001.o /home/ubuntu/lab/k9s/tmp/000002.o /home/ubuntu/lab/k9s/tmp/000003.o /home/ubuntu/lab/k9s/tmp/000004.o /home/ubuntu/lab/k9s/tmp/000005.o /home/ubuntu/lab/k9s/tmp/000006.o /home/ubuntu/lab/k9s/tmp/000007.o /home/ubuntu/lab/k9s/tmp/000008.o /home/ubuntu/lab/k9s/tmp/000009.o /home/ubuntu/lab/k9s/tmp/000010.o /home/ubuntu/lab/k9s/tmp/000011.o /home/ubuntu/lab/k9s/tmp/000012.o /home/ubuntu/lab/k9s/tmp/000013.o /home/ubuntu/lab/k9s/tmp/000014.o /home/ubuntu/lab/k9s/tmp/000015.o /home/ubuntu/lab/k9s/tmp/000016.o /home/ubuntu/lab/k9s/tmp/000017.o /home/ubuntu/lab/k9s/tmp/000018.o /home/ubuntu/lab/k9s/tmp/000019.o /home/ubuntu/lab/k9s/tmp/000020.o /home/ubuntu/lab/k9s/tmp/000021.o /home/ubuntu/lab/k9s/tmp/000022.o /home/ubuntu/lab/k9s/tmp/000023.o /home/ubuntu/lab/k9s/tmp/000024.o /home/ubuntu/lab/k9s/tmp/000025.o /home/ubuntu/lab/k9s/tmp/000026.o /home/ubuntu/lab/k9s/tmp/000027.o /home/ubuntu/lab/k9s/tmp/000028.o /home/ubuntu/lab/k9s/tmp/000029.o /home/ubuntu/lab/k9s/tmp/000030.o /home/ubuntu/lab/k9s/tmp/000031.o /home/ubuntu/lab/k9s/tmp/000032.o /home/ubuntu/lab/k9s/tmp/000033.o /home/ubuntu/lab/k9s/tmp/000034.o /home/ubuntu/lab/k9s/tmp/000035.o /home/ubuntu/lab/k9s/tmp/000036.o /home/ubuntu/lab/k9s/tmp/000037.o /home/ubuntu/lab/k9s/tmp/000038.o /home/ubuntu/lab/k9s/tmp/000039.o /home/ubuntu/lab/k9s/tmp/000040.o /home/ubuntu/lab/k9s/tmp/000041.o /home/ubuntu/lab/k9s/tmp/000042.o /home/ubuntu/lab/k9s/tmp/000043.o /home/ubuntu/lab/k9s/tmp/000044.o /home/ubuntu/lab/k9s/tmp/000045.o /home/ubuntu/lab/k9s/tmp/000046.o /home/ubuntu/lab/k9s/tmp/000047.o /home/ubuntu/lab/k9s/tmp/000048.o /home/ubuntu/lab/k9s/tmp/000049.o /home/ubuntu/lab/k9s/tmp/000050.o /home/ubuntu/lab/k9s/tmp/000051.o /home/ubuntu/lab/k9s/tmp/000052.o /home/ubuntu/lab/k9s/tmp/000053.o /home/ubuntu/lab/k9s/tmp/000054.o /home/ubuntu/lab/k9s/tmp/000055.o /home/ubuntu/lab/k9s/tmp/000056.o /home/ubuntu/lab/k9s/tmp/000057.o /home/ubuntu/lab/k9s/tmp/000058.o /home/ubuntu/lab/k9s/tmp/000059.o /home/ubuntu/lab/k9s/tmp/000060.o /home/ubuntu/lab/k9s/tmp/000061.o /home/ubuntu/lab/k9s/tmp/000062.o /home/ubuntu/lab/k9s/tmp/000063.o /home/ubuntu/lab/k9s/tmp/000064.o /home/ubuntu/lab/k9s/tmp/000065.o /home/ubuntu/lab/k9s/tmp/000066.o -O2 -g -O2 -g -lresolv -O2 -g -lpthread -O2 -g -ldl -O2 -g -no-pie -fuse-ld=lld

real	0m1.202s
user	0m2.228s
sys	0m0.389s
ubuntu@ip-172-31-47-209:~/lab/k9s$ time gcc -m64 -Wl,-z,now -Wl,-z,nocopyreloc -o /tmp/go-build1945961237/b001/exe/a.out -rdynamic -Wl,--compress-debug-sections=zlib /home/ubuntu/lab/k9s/tmp/go.o /home/ubuntu/lab/k9s/tmp/000000.o /home/ubuntu/lab/k9s/tmp/000001.o /home/ubuntu/lab/k9s/tmp/000002.o /home/ubuntu/lab/k9s/tmp/000003.o /home/ubuntu/lab/k9s/tmp/000004.o /home/ubuntu/lab/k9s/tmp/000005.o /home/ubuntu/lab/k9s/tmp/000006.o /home/ubuntu/lab/k9s/tmp/000007.o /home/ubuntu/lab/k9s/tmp/000008.o /home/ubuntu/lab/k9s/tmp/000009.o /home/ubuntu/lab/k9s/tmp/000010.o /home/ubuntu/lab/k9s/tmp/000011.o /home/ubuntu/lab/k9s/tmp/000012.o /home/ubuntu/lab/k9s/tmp/000013.o /home/ubuntu/lab/k9s/tmp/000014.o /home/ubuntu/lab/k9s/tmp/000015.o /home/ubuntu/lab/k9s/tmp/000016.o /home/ubuntu/lab/k9s/tmp/000017.o /home/ubuntu/lab/k9s/tmp/000018.o /home/ubuntu/lab/k9s/tmp/000019.o /home/ubuntu/lab/k9s/tmp/000020.o /home/ubuntu/lab/k9s/tmp/000021.o /home/ubuntu/lab/k9s/tmp/000022.o /home/ubuntu/lab/k9s/tmp/000023.o /home/ubuntu/lab/k9s/tmp/000024.o /home/ubuntu/lab/k9s/tmp/000025.o /home/ubuntu/lab/k9s/tmp/000026.o /home/ubuntu/lab/k9s/tmp/000027.o /home/ubuntu/lab/k9s/tmp/000028.o /home/ubuntu/lab/k9s/tmp/000029.o /home/ubuntu/lab/k9s/tmp/000030.o /home/ubuntu/lab/k9s/tmp/000031.o /home/ubuntu/lab/k9s/tmp/000032.o /home/ubuntu/lab/k9s/tmp/000033.o /home/ubuntu/lab/k9s/tmp/000034.o /home/ubuntu/lab/k9s/tmp/000035.o /home/ubuntu/lab/k9s/tmp/000036.o /home/ubuntu/lab/k9s/tmp/000037.o /home/ubuntu/lab/k9s/tmp/000038.o /home/ubuntu/lab/k9s/tmp/000039.o /home/ubuntu/lab/k9s/tmp/000040.o /home/ubuntu/lab/k9s/tmp/000041.o /home/ubuntu/lab/k9s/tmp/000042.o /home/ubuntu/lab/k9s/tmp/000043.o /home/ubuntu/lab/k9s/tmp/000044.o /home/ubuntu/lab/k9s/tmp/000045.o /home/ubuntu/lab/k9s/tmp/000046.o /home/ubuntu/lab/k9s/tmp/000047.o /home/ubuntu/lab/k9s/tmp/000048.o /home/ubuntu/lab/k9s/tmp/000049.o /home/ubuntu/lab/k9s/tmp/000050.o /home/ubuntu/lab/k9s/tmp/000051.o /home/ubuntu/lab/k9s/tmp/000052.o /home/ubuntu/lab/k9s/tmp/000053.o /home/ubuntu/lab/k9s/tmp/000054.o /home/ubuntu/lab/k9s/tmp/000055.o /home/ubuntu/lab/k9s/tmp/000056.o /home/ubuntu/lab/k9s/tmp/000057.o /home/ubuntu/lab/k9s/tmp/000058.o /home/ubuntu/lab/k9s/tmp/000059.o /home/ubuntu/lab/k9s/tmp/000060.o /home/ubuntu/lab/k9s/tmp/000061.o /home/ubuntu/lab/k9s/tmp/000062.o /home/ubuntu/lab/k9s/tmp/000063.o /home/ubuntu/lab/k9s/tmp/000064.o /home/ubuntu/lab/k9s/tmp/000065.o /home/ubuntu/lab/k9s/tmp/000066.o -O2 -g -O2 -g -lresolv -O2 -g -lpthread -O2 -g -ldl -O2 -g -no-pie -fuse-ld=mold

real	0m1.799s
user	0m0.008s
sys	0m0.001s
ubuntu@ip-172-31-47-209:~/lab/k9s$ time gcc -m64 -Wl,-z,now -Wl,-z,nocopyreloc -o /tmp/go-build1945961237/b001/exe/a.out -rdynamic -Wl,--compress-debug-sections=zlib /home/ubuntu/lab/k9s/tmp/go.o /home/ubuntu/lab/k9s/tmp/000000.o /home/ubuntu/lab/k9s/tmp/000001.o /home/ubuntu/lab/k9s/tmp/000002.o /home/ubuntu/lab/k9s/tmp/000003.o /home/ubuntu/lab/k9s/tmp/000004.o /home/ubuntu/lab/k9s/tmp/000005.o /home/ubuntu/lab/k9s/tmp/000006.o /home/ubuntu/lab/k9s/tmp/000007.o /home/ubuntu/lab/k9s/tmp/000008.o /home/ubuntu/lab/k9s/tmp/000009.o /home/ubuntu/lab/k9s/tmp/000010.o /home/ubuntu/lab/k9s/tmp/000011.o /home/ubuntu/lab/k9s/tmp/000012.o /home/ubuntu/lab/k9s/tmp/000013.o /home/ubuntu/lab/k9s/tmp/000014.o /home/ubuntu/lab/k9s/tmp/000015.o /home/ubuntu/lab/k9s/tmp/000016.o /home/ubuntu/lab/k9s/tmp/000017.o /home/ubuntu/lab/k9s/tmp/000018.o /home/ubuntu/lab/k9s/tmp/000019.o /home/ubuntu/lab/k9s/tmp/000020.o /home/ubuntu/lab/k9s/tmp/000021.o /home/ubuntu/lab/k9s/tmp/000022.o /home/ubuntu/lab/k9s/tmp/000023.o /home/ubuntu/lab/k9s/tmp/000024.o /home/ubuntu/lab/k9s/tmp/000025.o /home/ubuntu/lab/k9s/tmp/000026.o /home/ubuntu/lab/k9s/tmp/000027.o /home/ubuntu/lab/k9s/tmp/000028.o /home/ubuntu/lab/k9s/tmp/000029.o /home/ubuntu/lab/k9s/tmp/000030.o /home/ubuntu/lab/k9s/tmp/000031.o /home/ubuntu/lab/k9s/tmp/000032.o /home/ubuntu/lab/k9s/tmp/000033.o /home/ubuntu/lab/k9s/tmp/000034.o /home/ubuntu/lab/k9s/tmp/000035.o /home/ubuntu/lab/k9s/tmp/000036.o /home/ubuntu/lab/k9s/tmp/000037.o /home/ubuntu/lab/k9s/tmp/000038.o /home/ubuntu/lab/k9s/tmp/000039.o /home/ubuntu/lab/k9s/tmp/000040.o /home/ubuntu/lab/k9s/tmp/000041.o /home/ubuntu/lab/k9s/tmp/000042.o /home/ubuntu/lab/k9s/tmp/000043.o /home/ubuntu/lab/k9s/tmp/000044.o /home/ubuntu/lab/k9s/tmp/000045.o /home/ubuntu/lab/k9s/tmp/000046.o /home/ubuntu/lab/k9s/tmp/000047.o /home/ubuntu/lab/k9s/tmp/000048.o /home/ubuntu/lab/k9s/tmp/000049.o /home/ubuntu/lab/k9s/tmp/000050.o /home/ubuntu/lab/k9s/tmp/000051.o /home/ubuntu/lab/k9s/tmp/000052.o /home/ubuntu/lab/k9s/tmp/000053.o /home/ubuntu/lab/k9s/tmp/000054.o /home/ubuntu/lab/k9s/tmp/000055.o /home/ubuntu/lab/k9s/tmp/000056.o /home/ubuntu/lab/k9s/tmp/000057.o /home/ubuntu/lab/k9s/tmp/000058.o /home/ubuntu/lab/k9s/tmp/000059.o /home/ubuntu/lab/k9s/tmp/000060.o /home/ubuntu/lab/k9s/tmp/000061.o /home/ubuntu/lab/k9s/tmp/000062.o /home/ubuntu/lab/k9s/tmp/000063.o /home/ubuntu/lab/k9s/tmp/000064.o /home/ubuntu/lab/k9s/tmp/000065.o /home/ubuntu/lab/k9s/tmp/000066.o -O2 -g -O2 -g -lresolv -O2 -g -lpthread -O2 -g -ldl -O2 -g -no-pie -fuse-ld=gold

real	0m3.003s
user	0m2.612s
sys	0m0.391s
nasanasa

計測方法

そう言えば書いてなかった。

go build -x -workでオブジェクトファイルを生成。go tool linkコマンドがログに出るのでメモる

例えば↓のようなコマンドが表示されるので-fuse-ld-linkmodeを変更しつつ計測する。

/usr/local/go/pkg/tool/linux_amd64/link -benchmark cpu -tmpdir /home/ubuntu/lab/k9s/tmp -v -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=ygYuj0j-uY17HTJOQLGd/9vII3OitFgUV5O-yJyIJ/tTAPQeaUNB_jLpmGaXme/ygYuj0j-uY17HTJOQLGd -linkmode=external -cpuprofile=/home/ubuntu/lab/k9s/cpuprofile -memprofile=/home/ubuntu/lab/k9s/memprofile -extldflags=-fuse-ld=mold -extld=gcc /home/ubuntu/.cache/go-build/82/8291d490e6b18f3ee83f0a5ad1f70b372370d4acf8fe25f29911bfe499cad222-d

こうしているのはgo buildだとリンク以外の時間がどうしても紛れ込むので。
といっても一度buildした後だとキャッシュが効いてリンク以外で対した時間はかからないが