Open10

containerd から Intel ISA-L の igzip を使う

加藤和良加藤和良

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
加藤和良加藤和良

コメントをもらう

  1. 環境変数チェックしてから exec.LookPath したらいいのでは?
  2. CONTAINERD_DISABLE_PIGZ / CONTAINERD_DISABLE_IGZIP という設定はどうなの
  3. これ全然ドキュメントないね

1 は簡単なのでさくっと修正。2と3は、それをいいだすとそもそも環境変数で設定できるというのが微妙というか、/etc/containerd/config.toml にあってしかるべきだよなあと思う。

NO_COLOR みたいな複数のソフトウェアにまたがるものならわかるけど、containerd 特有の設定は環境変数になくていいよねえ。

ありえるのいのうえさんも昔に書いていた

最近、続けて環境変数ではまっている人を見て思ったのが、今や環境変数は弊害の方が大きいのではないか、ということです。どの環境変数が影響するかを調べるには、ltrace(1)でgetenv(3)の呼び出しを調べれば分かりそうですが、かなり大変です。オブジェクトファイルからの静的な解析は原理的には無理そうです。env(1)とstrings(1)の出力の共通部分を表示すれば少し近い結果が得られるかもしれませんが、精度は低そうです。もっとも、今挙げた話は現実的にはナンセンスです。なぜなら、そもそも「環境変数が悪さをしている」という事実に気づかない(気づきにくい)点こそが問題だからです。