Chienomi

cgroups v2を使って1コアCPU環境でテストする

Live With Linux::technique

動機としては、動画エンコーディングの際になぜかCPUの利用率を無視してどのコーデックが速いの遅いのという話をされがちなので、シングルコアのCPU上でテストしたかったというもの。

これを実現するために、おおまかにはcgroupsを使う方法とtasksetを使う方法がある。 このうち、cgroups v2を使う方法は調べてもあまり出てこないので、ここにまとめることにした。

そもそもcgroupsとは

cgroupsは、名前のまんまだが、リソースや名前空間を制御するコントロールグループを取り扱うものである。

プロセスをコントロールグループに所属させることで、namespaceなどの機能を適用することができる。

リソース制御の機能そのものではなく、それらをまとめるもののこと。

cgroups v2を使う

cgroupsの使い方は色々あるけれど、cgroups2fsを使うのがおそらく最も簡単で汎用性がある。

ここではcgroups2/sys/fs/cgroupにマウントされている前提で話を進める。

cgroups2ファイルシステムにディレクトリを作ると、新たなコントロールグループが作成される。

# mkdir /sys/fs/cgroup/sandboxxx

コントロールグループディレクトリ以下には自動的にコントローラが配置される。

cgroup.controllers
cgroup.events
cgroup.freeze
cgroup.kill
cgroup.max.depth
cgroup.max.descendants
cgroup.pressure
cgroup.procs
cgroup.stat
cgroup.threads
cgroup.type
cgroup.subtree_control
cpu.idle
cpu.max
cpu.max.burst
cpu.pressure
cpu.stat
cpu.uclamp.max
cpu.uclamp.min
cpu.weight
cpu.weight.nice
io.pressure
irq.pressure
memory.current
memory.events
memory.events.local
memory.high
memory.low
memory.max
memory.min
memory.oom.group
memory.peak
memory.pressure
memory.reclaim
memory.stat
memory.swap.current
memory.swap.events
memory.swap.high
memory.swap.max
memory.zswap.current
memory.zswap.max
memory.numa_stat
pids.current
pids.events
pids.max
pids.peak

CPU数の制御にはcpusetコントローラを使用するが、この場合は有効になっていないことが見て取れる。

そこで、まずcgroups v2上でcpusetコントローラを有効化する。

# echo "+cpuset" >> /sys/fs/cgroup/cgroup.subtree_control

続いて、sandboxxxコントロールグループに対してcpusetコントローラを有効にする。

# echo "+cpuset" > /sys/fs/cgroup/sandboxxx/cgroup.subtree_control

cpusetコントローラを使い、sandboxxxコントロールグループにはプロセッサ0だけを割り当てる。

# echo 0 > /sys/fs/cgroup/sandboxxx/cpuset.cpus

そして、cgroup.procsコントローラにこのコントロールグループを適用するプロセスを記入する。 ここではpid 10000のプロセスに適用した。

echo "10000" > /sys/fs/cgroup/sandboxxx/cgroup.procs

子プロセスに対しても適用されるので、シェルプロセスを指定すると扱いやすい。

tasksetを使う

tasksetを使うとプロセスは指定されたCPUを使うようになる。 結果はcgroupsと似たようなものではあるが、cgroupsの場合はプロセスから見て利用できるCPUそのものが限定されているのに対し、tasksetを使って制限した場合はプロセスがどんなにマルチスレッドで動作しても、Linuxカーネルは指定されたCPUの時間しか割り当てない。

tasksetはユーザーレベルで実行可能なのもメリット。次のようにしてZshログインシェルをCPU0のみを使用する状態で起動する。

% taskset -c 0 zsh -l

cpulimitを使う

cpulimitを使うとCPU時間をパーセンテージで制限することができる。

これはLinuxの基本機能ではなく、cpulimitというソフトウェアがある。

% cpulimit -l 100 -p 10000

とすると、pid 10000のプロセスのCPU利用量を100%(CPUコア1つ分)に制限する。

この方法はCPU時間割当があまり安定せず、CPUリソースの制限された環境をテストするのには向かない印象を受けた。

cgroups v2でCPUを時間量的に制限する

cpuコントローラのcpu.maxを使えばcgroups v2を用いてCPU帯域を制限することができる。

書式は

$MAX $PERIOD

であり、$PERIOD期間中$MAXだけCPU時間を使うことを許可する。デフォルトはmax 10000.

注意してほしいのは、

10000 10000

は無制限を意味しないということだ。 この場合、CPUを100%、つまり1コア分相当だけ使うことを許可することになる。16コアであれば最大値は160000になる。

先程のsandboxxxにCPU利用率200%を許可する場合、

# echo "20000 10000" > /sys/fs/cgroup/sandboxxx/cpu.max

となる。

このようにすれば、擬似的にCPUリソースの乏しい環境をテストできる。

で、テストの結果は?

Street Fighter 6のプレイ動画を使ってエンコードした結果。動きの激しさが変化するので、だいたい1:00あたりの速度を計測した。

1コアだけを割り当てているので、CPU時間に対する効率と考えて良い。 速度に関連するファクターは与えず、画質調整によってサイズだけをある程度揃えるようにした。 ただし、libaom-av1に関しては極端に時間がかかるため、サイズを揃える作業はせず、単に-crf 34を与えている。

  • 23fps libx264 (H.264)
  • 10fps libx265 (HEVC)
  • 10fps libvpx (VP8)
  • 5.5fps libvpx-vp9 (VP9)
  • <0.1fps libaom-av1 (AV1)
  • 17fps svtav1 (AV1)

あれ、SVT-AV1ってこんなに速かったっけ?

Netflixが開発するSVT-AV1コーデックだが、恐ろしいほど遅いlibaom-av1と比べてかなり高速ということで話題になった。 ただ、初期に私が試した限りでは並列化が得意なだけで、CPU時間比だとあんまり変わらないという印象だった。

ただ、CRF値がlibvpx-vp9やlibaom-av1とはかなり異なっているため、サイズを小さくしようとすると調整が必要。 また、libvpx-vp9やlibaom-av1にあるConstrained Qualityがないため、調整が難しいほか、libvpx-vp9と比べてQuality/Bitrateの比が悪い。つまり、libvpx-vp9によるVP9動画と同等の視覚品質を得ようとすると、svtav1のほうがファイルサイズが大きくなる。 特にlibvpx-vp9はゲーム動画のような、画面に部分的に動きがあったりなかったりするような動画をかなり小さく圧縮するため、ゲームキャプチャのエンコードとしてはやや微妙かもしれない。

ただ、libx265比でも速く、並列化も(libx265ほどではないにせよ)効くので、libvpx-vp9で設定を詰めるほどではないが、実時間で速くエンコードしたい場合などには使えそう。

アーカイブに使うにはサイズ、品質ともにやや厳しい。 libvpx-vp9と比べると3〜5倍程度のビットを割り当てないとうまくいかない印象だった。

だいたい10Mbps程度でcvbrにすると悪くないので、ハードウェアエンコーディングに近い印象かもしれない。 ただ、性質がハードウェアエンコーディングに近すぎるため、ハードウェアエンコーディングできる環境では選択理由があまりない感じだ。