bokumin.org

Github RSS Sitemap Mail
ritual-clearcut

Linux 7.0のzram Writebackを試す(Slackware)

Testing zram writeback on Linux 7.0 (Slackware)

はじめに

Linux カーネル7.0ではzramにいくつかの改善が加えられました。今回は実際に実機を動かして確認して行こうと思います。

Kernel 7.0でのzramの変更点

  • 圧縮データライトバック(Writeback)が実装された
    • 変更前はディスクへ書き出す際に一度展開してから書き込んでいた
          変更後は圧縮されたまま直接ディスクに書き込む

ライトバックの実装で、CPUサイクルの節約、バッテリー消費の削減ができるようになったとのことです。

また、カーネル4.15(2018年)では、ZRAMやSSDのような高速なswapデバイスに対して、swapキャッシュを経由せず直接処理するバイパス機能が追加されました。 当時はswapキャッシュ自体が遅かったため有効な最適化だったのですが、コードが複雑になりバグの温床にもなっていました
カーネル7.0ではKairui Songによってswapキャッシュ自体が高速化され(swap table phase II)、バイパスを経由しなくてもパフォーマンスが落ちない状態になりました。 そのためバイパス機能を丸ごと削除し、コードをシンプルにしています。 Redisの永続化ありベンチマークでは約20%の性能向上が確認されています。
参考URL: https://lkml.org/lkml/2025/12/20/15

実行した環境

OS: Slackware 15.0
Kernel: 7.0
CPU: Intel(R) Celeron(R) N4100 CPU @ 2.400GHz
RAM: 4GB+8GB(11.7GB)
NVMe swap: 8GB (priority 1)
zram:12 GB (priority100)

$ cat /sys/block/zram0/comp_algorithm
lzo-rle lzo lz4 lz4hc [zstd] deflate 842 

zstdのように括弧がついているものが現在選択中のアルゴリズムです。

次にKernel 7.0の新機能ノードを確認します。

$ ls /sys/block/zram0/ | grep writeback
compressed_writeback
...

compressed_writebackが表示されればカーネル7.0の圧縮ライトバックに対応済みです。

Writeback動作確認

それでは実際にメモリを消費した際にzramがどのように動作するかテストしていきます。バッキングデバイスを設定していない通常のzram運用では今回の新機能の恩恵はないため、テスト用に4GBのバッキングデバイスを作成して検証しました。

sudo dd if=/dev/zero of=/backing_test bs=1M count=4096 status=progress
$ sudo swapoff -a

swapoffが無事に成功したら、以下のスクリプトを作成しroot権限で実行します。
ZRAM_SIZEを2GBに指定していますが、お使いの環境に合わせて変更することをおすすめします。
圧縮率よりも速度重視でlzo-rleを指定していますが、zstdでも問題ありません。

#!/bin/bash

[ "$EUID" -ne 0 ] && exit 1

BACKING_FILE="/backing_test"
ZRAM_SIZE="2G"
ZRAM_COMP="lzo-rle"
ZRAM_PRIORITY="100"

[ ! -f "$BACKING_FILE" ] && exit 1

modprobe zram 2>/dev/null

ZRAM_DEVICE=$(zramctl -f)
[ -z "$ZRAM_DEVICE" ] && exit 1

ZRAM_NAME="${ZRAM_DEVICE##*/}"

LOOP_DEV=$(losetup -f --show "$BACKING_FILE")
[ -z "$LOOP_DEV" ] && exit 1

echo "$LOOP_DEV" > /sys/block/${ZRAM_NAME}/backing_dev
echo "yes" > /sys/block/${ZRAM_NAME}/compressed_writeback
echo "$ZRAM_COMP" > /sys/block/${ZRAM_NAME}/comp_algorithm
echo "$ZRAM_SIZE" > /sys/block/${ZRAM_NAME}/disksize

mkswap "$ZRAM_DEVICE" 1>/dev/null 2>/dev/null
swapon --priority "$ZRAM_PRIORITY" "$ZRAM_DEVICE"
echo "$ZRAM_DEVICE"

compressed_writeback はデフォルトで no(無効)です。有効にするには backing_dev の設定後、disksize(デバイス初期化)のに設定する必要があります。
※上記のスクリプトではすでに有効化済みです。

スクリプトを実行後、以下のように有効化されていれば成功です。

$ sudo swapon --show
NAME       TYPE      SIZE USED PRIO
/dev/zram1 partition   2G   0G  100

compressed_writeback=no と yes の比較

compressed_writeback はデフォルトで no のため、実際に no と yes でどう違うかを同一条件(同程度のzram使用量)でそれぞれ複数回writebackを実行して比較しました。

設定same_pages(平均)zram圧縮率(平均)
no2763.3x
yes1309(約4.7倍)4.9x(最大7.5x)

same_pages が yes で大幅に増加している理由は、yes では圧縮しにくいページ(incompressible pages)を優先的にバッキングデバイスへ追い出すためです。その結果、zram上に残るページがより均一・重複したデータになり、圧縮率も向上しています。

なお、今回のテストではバッキングデバイスにループデバイス(/dev/loop0)を使用しています。ループデバイスは内部で4KBセクタ境界へのアライメントを強制するため、圧縮後データが小さくても常に4KB単位で書き込まれます。そのため no と yes でI/O書込みバイト数に差は現れませんでした。実際のNVMeやeMMCをバッキングデバイスとして使用した場合は、圧縮率に応じた書き込みバイト数の削減も期待できると思います。

メモリ消費テスト

次に、メモリを手動で消費していきます。
メモリ消費プログラムはPythonで作成しています。別窓で実行してみてください。
※現在は最大9GBを消費するようになっていますが、持ち前の環境によってSWAPが発生する値に変更していただければと思います。

import time
data = []
limit_mb = 9000
try:
    while True:
        data.append(bytearray(100 * 1024 * 1024))
        used = len(data) * 100
        print(f'{used} MB', flush=True)
        if used >= limit_mb:
            print(f'Reached limit: {used} MB')
            break
except MemoryError:
    print(f'MemoryError at {len(data) * 100} MB')
print('Ctrl+C to release')
while True:
    time.sleep(1)

これを動かすと、Pythonがメモリを消費して行く様子が確認できます。以下実行中のtopコマンドです。

top - 00:31:31 up 5 min,  1 user,  load average: 1.53, 1.41, 0.65
Tasks: 261 total, 1 running, 260 sleep, 0 d-sleep, 0 stopped, 0 zombie
%Cpu(s):  8.2 us,  7.1 sy,  0.0 ni, 82.7 id,  2.0 wa,  0.0 hi,  0.0 si,  0.0 st 
MiB Mem :  11775.9 total,    369.4 free,  11428.7 used,    707.5 buff/cache     
MiB Swap:   2048.0 total,   1637.8 free,    410.2 used.    347.2 avail Mem 

お待ちかねのライトバックを実行します。zramのデバイス名はスクリプト実行時に表示されたものを使用してください。今回の環境ではzram1でしたので、それを指定していきます。

3種類のライトバック対象を選択できます。通常は type=huge_idle(圧縮できず、かつ長時間アクセスされていないページ)を対象にするのが最もRAMを効率よく解放できます。type=idle や type=huge は単体でも使えますが、フラッシュデバイスへの書き込み回数が増える点に注意が必要です。

# すべてのページを idle としてマーク
echo all | sudo tee /sys/block/zram1/idle

# idle ページをライトバック
echo "type=idle" | sudo tee /sys/block/zram1/writeback

# huge ページをライトバック
echo "type=huge" | sudo tee /sys/block/zram1/writeback

# huge かつ idle ページをライトバック
echo "type=huge_idle" | sudo tee /sys/block/zram1/writeback

bd_stat、mm_statを確認していきます。

$ cat /sys/block/zram1/bd_stat
  292244     1801   294048
$ cat /sys/block/zram1/mm_stat
1204899840        0        0        0 433520640     3463     2287        0    10090

bd_stat の3つの数値はそれぞれ、現在バッキングデバイスに退避中のページ数・読み戻し回数・書き込み回数を表しています。今回の結果では論理データサイズで約1142MB分のページがライトバック済みで、読み戻しが少ないことから効率よく動作していることがわかります。mm_stat を見るとメモリ上の圧縮データサイズが0MBになっていますが、これはzram内のデータが展開(解凍)処理されることなく、圧縮された状態のままバッキングデバイスへ送られたためです。これにより、すべてのデータが無駄な処理なくループデバイス等に逃がされてzramのメモリ消費が実質ゼロとなり、システムで使える物理メモリ量が大幅に増加しました。

CPU負荷も同時に検証すればよかったですね・・

まとめ

Writeback自体はカーネル4.14から存在していましたが、カーネル7.0では圧縮データをそのままライトバックできるようになりました。以前はバッキングデバイスへの書き出し時に展開処理が走っていたため、余分なCPU負荷と消費電力が発生していました。今回の改善により、zramで圧縮されたページをそのまま書き出せるようになり、メモリ効率がさらに向上しています。

なお、compressed_writeback はデフォルトで no のため、恩恵を受けるには明示的に有効化する必要があります。また、バッキングデバイスにループデバイスを使う場合はI/O削減効果が数値に現れにくい点もあわせて覚えておくと良いと思います。

メモリが少ない環境や、省電力を重視する環境では効果が大きく、バッキングデバイスとしてNVMeなどの高速ストレージを用意できる場合は試してみる価値があるかなと感じました。

おわり