goでmoldを試す
go言語のリンクは外部のリンカで行われる場合と内部リンカで行われる場合があるらしい。
内部リンカはgoで書かれているもので、外部リンカより高速なので出来るだけコチラを使うみたい。
でもcgoが絡んでくるとリンクできないのでこのときは外部リンカを使うらしい。
詳しい条件は知らない。
内部リンカは高速だと言っているがmoldと比べたらどうなんだろう?と気になったので試してみた。
ついでに他のリンカとの速度比較も行ってみた。
外部リンカを呼ぶ方法
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 → 任意のリンカというようにいくつも経由して呼び出している。
任意のリンカを呼べる所まで来たので計測してみよう。
とりあえず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 |
感想。
gccの仕事がわからないので今回計測できたのは gcc + リンカの実行時間になっている。
外部リンカを呼び出すのにもコストがかかるのでそれで時間がかかっているのかもしれない。
まあとりあえず内部リンカが一番早いのでこれを使っておけば良いみたい。
なんで速いのかは後日調べてみる。
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時間であり現実時間じゃない)
リンカのソースコードを読む
exec.Command
でリンカを呼んでる。
pprofでは0.03s以下の呼び出しは表示されない
つまり微々たる差なのか?
だが、go build
の実行時間には数秒の差がある。
pprofにexec.Commandが現れないのかも
リンカのログを読む
ちゃんと調べるとリンカにも詳細ログを出力するオプションがあった。(ソースコードを読んでて気づいた、、、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
計測方法
そう言えば書いてなかった。
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した後だとキャッシュが効いてリンク以外で対した時間はかからないが