containerd から Intel ISA-L の igzip を使う
Intel Intelligent Storage Acceleration Library というのがあって、これの gzip は速い。私は Volker Simonis さんのベンチマークで知った。
今回は、これを containerd から使えるようにしてみます。
containerd は Go で書かれているので、gzip は標準ライブラリの compress/gzip が使えるのだけど、さらに pigz があるとそれを使うようになっている。なので、そこらへんをいじればいいはず。
そのまえに ISA-L のインストール。Makefile.unx という素直な Makefile がついてきて気が利いているが make install
しようとしたら install
は定義されていなく、結局 autoconf を使う方法にした。nasm と libtool がはいっていなかったので apt からインストール。
sudo make install
したら sudo ldconfig
でキャッシュを更新する。
Go が古かったのでインストールする。
% go install golang.org/dl/go1.21.2@latest
% ~/go/bin/go1.21.2 download
...
Success. You may now run 'go1.21.2'
% PATH=$HOME/sdk/go1.21.2/bin:$PATH
make できた。
% make
...
+ bin/containerd-shim-runc-v2
+ binaries
make 213.29s user 31.07s system 875% cpu 27.918 total
archive/compression/compression.go で pigz をあつかっているので、ここを変更。変更中の diff をスクラップにのせるのは面倒だなあ。メインの変更はここらへん。
func gzipDecompress(ctx context.Context, buf io.Reader) (io.ReadCloser, error) {
initGzip.Do(func() {
if gzipPath = detectCommand("igzip", "CONTAINERD_DISABLE_IGZIP"); gzipPath != "" {
log.L.Debug("using igzip for decompression")
return
}
if gzipPath = detectCommand("unpigz", "CONTAINERD_DISABLE_PIGZ"); gzipPath != "" {
log.L.Debug("using unpigz for decompression")
}
})
if gzipPath == "" {
return gzip.NewReader(buf)
}
return cmdStream(exec.CommandContext(ctx, gzipPath, "-d", "-c"), buf)
}
すでにある unpigzPath に igzipPath を足そうかと思ったけど、コマンドライン引数が -d -c を渡すところとか共通点が多いので、gzipPath にまとめた。
ここらへんちゃんとベンチマークテストがついているので、そこでも igzip と pigz を比較できるようにする。
func BenchmarkDecompression(b *testing.B) {
resp, err := http.Get(benchmarkTestDataURL)
require.NoError(b, err)
data, err := io.ReadAll(resp.Body)
require.NoError(b, err)
resp.Body.Close()
const mib = 1024 * 1024
sizes := []int{32, 64, 128, 256}
for _, sizeInMiB := range sizes {
size := sizeInMiB * mib
for len(data) < size {
data = append(data, data...)
}
data = data[0:size]
gz := testCompress(b, data, Gzip)
zstd := testCompress(b, data, Zstd)
b.Run(fmt.Sprintf("size=%dMiB", sizeInMiB), func(b *testing.B) {
original := gzipPath
defer func() {
gzipPath = original
}()
b.Run("zstd", func(b *testing.B) {
testDecompress(b, zstd)
})
gzipPath = ""
b.Run("gzipPureGo", func(b *testing.B) {
testDecompress(b, gz)
})
gzipPath, err = exec.LookPath("igzip")
if err == nil {
b.Run("igzip", func(b *testing.B) {
testDecompress(b, gz)
})
}
gzipPath, err = exec.LookPath("unpigz")
if err == nil {
b.Run("unpigz", func(b *testing.B) {
testDecompress(b, gz)
})
}
})
}
}
ベンチマーク。ちゃんと速くなっていて安心するけど、Go のベンチマークは一回まわしたときの速度に応じて繰り返しの数を変えるのでちょっとわかりづらいな。
% go test -bench=BenchmarkDecompression
goos: linux
goarch: amd64
pkg: github.com/containerd/containerd/archive/compression
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkDecompression/size=32MiB/zstd-16 1000000000 0.1212 ns/op
BenchmarkDecompression/size=32MiB/gzipPureGo-16 1000000000 0.2354 ns/op
BenchmarkDecompression/size=32MiB/igzip-16 1000000000 0.1367 ns/op
BenchmarkDecompression/size=32MiB/unpigz-16 1000000000 0.2197 ns/op
BenchmarkDecompression/size=64MiB/zstd-16 1000000000 0.2668 ns/op
BenchmarkDecompression/size=64MiB/gzipPureGo-16 1000000000 0.6376 ns/op
BenchmarkDecompression/size=64MiB/igzip-16 1000000000 0.3605 ns/op
BenchmarkDecompression/size=64MiB/unpigz-16 1000000000 0.5686 ns/op
BenchmarkDecompression/size=128MiB/zstd-16 1000000000 0.3959 ns/op
BenchmarkDecompression/size=128MiB/gzipPureGo-16 1 1056064432 ns/op
BenchmarkDecompression/size=128MiB/igzip-16 1000000000 0.4904 ns/op
BenchmarkDecompression/size=128MiB/unpigz-16 2 519001279 ns/op
BenchmarkDecompression/size=256MiB/zstd-16 1 1064701762 ns/op
BenchmarkDecompression/size=256MiB/gzipPureGo-16 1 2007529668 ns/op
BenchmarkDecompression/size=256MiB/igzip-16 1 1183161763 ns/op
BenchmarkDecompression/size=256MiB/unpigz-16 1 1949913012 ns/op
PASS
ok github.com/containerd/containerd/archive/compression 110.920s
go test -bench=BenchmarkDecompression 108.87s user 43.65s system 137% cpu 1:51.33 total
プルリクにした。ベンチマークは -benchtime=1000000000x
で実行回数をそろえてプルリクエストのコメントに足しておいた。
コメントをもらう
- 環境変数チェックしてから exec.LookPath したらいいのでは?
- CONTAINERD_DISABLE_PIGZ / CONTAINERD_DISABLE_IGZIP という設定はどうなの
- これ全然ドキュメントないね
1 は簡単なのでさくっと修正。2と3は、それをいいだすとそもそも環境変数で設定できるというのが微妙というか、/etc/containerd/config.toml にあってしかるべきだよなあと思う。
NO_COLOR みたいな複数のソフトウェアにまたがるものならわかるけど、containerd 特有の設定は環境変数になくていいよねえ。
ありえるのいのうえさんも昔に書いていた。
最近、続けて環境変数ではまっている人を見て思ったのが、今や環境変数は弊害の方が大きいのではないか、ということです。どの環境変数が影響するかを調べるには、ltrace(1)でgetenv(3)の呼び出しを調べれば分かりそうですが、かなり大変です。オブジェクトファイルからの静的な解析は原理的には無理そうです。env(1)とstrings(1)の出力の共通部分を表示すれば少し近い結果が得られるかもしれませんが、精度は低そうです。もっとも、今挙げた話は現実的にはナンセンスです。なぜなら、そもそも「環境変数が悪さをしている」という事実に気づかない(気づきにくい)点こそが問題だからです。