iowaitはいつ見えなくなるか

先日twitter(X)でiowaitが云々で盛り上がっていて、それ自体は別にどうという話でもなかったのですが、iowaitって今実際どうなってるんだろう?というところを少し実験してみました。

目的

I/Oボトルネックのプログラム動作中にvmstatのiowaitがいつ見えなくなるか調べる

iowait?

iowaitが分かるのはtopコマンドやvmstatです。Ubuntuではどちらもprocpsパッケージに含まれています。このパッケージは以前も調べてるので調べ方は以下を参考にしてください。

vmstatのiowaitに使用されているのは、/proc/statファイルのiowaitでした。procps-3.3.17/vmstat.cの306行目で呼ばれているgetstat()がprocps-3.3.17/proc/sysinfo.cにあり、そこで読まれているのをデバッガで確認しました。

/proc/statファイルのiowaitはman procfsを参照してください。

iowait (since Linux 2.5.41)
(5) Time waiting for I/O to complete.  This value is not reliable, for the following reasons:
1. The  CPU  will  not  wait for I/O to complete; iowait is the time that a task is waiting for I/O to complete.  When a CPU goes into idle state for
outstanding task I/O, another task will be scheduled on this CPU.
2. On a multi-core CPU, the task waiting for I/O to complete is not running on any CPU, so the iowait of each CPU is difficult to calculate.
3. The value in this field may decrease in certain conditions.
man procfs

ようはI/O待ちしてる時間なんだが、待っているタスクの他に実行してるタスクがあればそちらがCPU使って減っていくもので、信用ならんそうです。

なので今回は、どう減っていくのか? を検証したいと思います。

検証

コードは以下のリポジトリにあります。

https://git.elephantcat.work/first_user/iowait_test

I/O待ちを行うプロセス

I/O待ちを行うプロセスを可能な限り原始的に作ったのが以下です。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdint>
int main() {
    int f = open("hoge.txt", O_CREAT|O_SYNC|O_RDWR, 0644);
    uint8_t data[4096] = {0};
    while(true) {
        write(f, data, 4096);
        lseek(f, 0, SEEK_SET);
    }
    close(f);
    return 0;
}

O_SYNC(データの書き込みが終わるまでブロックされる)で開いたファイルを4KB書き込み、終わったら先頭からそれを繰り返すだけのプログラムです。無限ループするので、終了にはCtrl+Cなどが必要です。

これをg++ -g hoge.cpp -o hogeなどしてビルドし、端末上で実行しながら、別端末でvmstatをかけると以下のような結果になります。

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1  82952 383260 158132 1915032    0    2    35    88  497  359  3  1 90  6  0
 0  1  82952 383260 158152 1915068    0    0     0   128 1180  881  1  2 49 49  0
 0  1  82952 383260 158176 1915056    0    0     0   632 1093  964  1  1 49 49  0
 0  1  82952 383260 158220 1915064    0    0     0   192 1107  945  2  1 48 49  0
 0  1  82952 383260 158236 1915068    0    0     0   144 1161  954  1  2 48 49  0
 1  1  82952 383260 158264 1915060    0    0     0   152 1144  933  1  1 49 49  0
^C
$

VirtualBoxで2コア割り当てたUbuntuなので、49%のI/O WAITが発生しています。もう約半分の49%がIDLEですね。ふむふむ、確かにI/O待ちが見えています。

段々忙しくして、vmstatの変化を見る

cat >hoge.cpp <<EOF
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdint>
int main() {
    int f = open("hoge.txt", O_CREAT|O_SYNC|O_RDWR, 0644);
    uint8_t data[4096] = {0};
    while(true) {
        write(f, data, 4096);
        lseek(f, 0, SEEK_SET);
    }
    close(f);
    return 0;
}
EOF
g++ -g hoge.cpp -o hoge
for pcount in $(seq 0 $(nproc --all)); do
    echo "[dummy process count: $pcount]"
    /usr/bin/time ./hoge&
    pid_time=$!
    pid_dummy=''
    for i in $(seq $pcount); do
        sh -c 'while :;do :;done'&
        pid_dummy="$pid_dummy $!"
    done
    vmstat 1 10
    pid_hoge=$(ps -o pid= --ppid $pid_time)
    kill $pid_hoge $pid_dummy
    wait
done

今度は先のプログラムを吐き出してコンパイルし、それを動かしながら、ただビジーループするシェルスクリプトをCPU/コア数分だけ実行するシェルスクリプトです。nproc –allがCPU/コア数を取得するコマンドで、/usr/bin/timeは時間測定用のコマンドです。それ以外は説明が必要なほどではないと思います。

これを2CPU/コアの環境で実行すると以下のようになります。

[dummy process count: 0]
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1  82952 433764 160024 1893676    0    2    34    88  498  360  3  1 90  6  0
 0  1  82952 433640 160056 1893780    0    0     0   216 1189  874  2  2 49 48  0
 0  1  82952 433640 160088 1893788    0    0     0   148 1236  881  1  1 49 49  0
 0  2  82952 433640 160108 1893780    0    0     0   172 1203  969  2  2 41 56  0
 0  1  82952 433640 160140 1893784    0    0     0   252 1338 1059  1  2 28 69  0
 0  1  82952 433640 160176 1893780    0    0     0   140 1109  895  1  1 49 49  0
 0  1  82952 433640 160196 1893784    0    0     0    80  993  790  1  1 49 49  0
 0  1  82952 433640 160224 1893780    0    0     0   112 1250  983  1  1 49 49  0
 1  1  82952 433144 160256 1893784    0    0     0    84 1184  896  1  2 49 48  0
 1  0  82952 433020 160300 1893776    0    0     0   392 1153  979  1  1 49 49  0
Command terminated by signal 15
0.00user 0.01system 0:09.17elapsed 0%CPU (0avgtext+0avgdata 896maxresident)k
0inputs+544outputs (0major+61minor)pagefaults 0swaps
[dummy process count: 1]
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  2  82952 432400 160312 1894064    0    2    34    88  498  360  3  1 90  6  0
 1  1  82952 432276 160336 1893804    0    0     0   144  854  835 52  1  3 45  0
 1  1  82952 432276 160372 1893804    0    0     0   220  845 1016 52  1  0 47  0
 1  1  82952 432276 160428 1893784    0    0     0   160  853 1208 52  1  0 47  0
 1  1  82952 432276 160476 1893792    0    0     0   144  844 1198 52  1  0 48  0
 1  1  82952 432276 160480 1893816    0    0     0   144  813 1103 53  1  0 46  0
 1  1  82952 432276 160480 1893816    0    0     0   132  791  998 52  1  0 48  0
 1  1  82952 432276 160512 1893796    0    0     0   136  873 1184 52  0  0 48  0
 1  1  82952 432276 160536 1893800    0    0     0    80  795 1170 51  1  0 48  0
 1  1  82952 432276 160560 1893812    0    0     0   612  816 1223 52  1  0 47  0
Command terminated by signal 15
0.00user 0.01system 0:09.18elapsed 0%CPU (0avgtext+0avgdata 896maxresident)k
0inputs+512outputs (0major+60minor)pagefaults 0swaps
[dummy process count: 2]
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  1  82952 432276 160564 1893808    0    2    34    88  498  360  3  1 90  6  0
 3  1  82952 432028 160608 1893788    0    0     0   480  926  847 100  1  0  0  0
 2  1  82952 432028 160640 1893804    0    0     0   168  901  835 100  0  0  0  0
 2  1  82952 432028 160692 1893792    0    0     0   220  983  882 100  0  0  0  0
 2  1  82952 432028 160744 1893784    0    0     0   128  909  885 100  0  0  0  0
 2  1  82952 432028 160772 1893804    0    0     0   112  976  797 100  1  0  0  0
 2  1  82952 432028 160816 1893788    0    0     0   320  829  850 100  0  0  0  0
 2  1  82952 432028 160872 1893780    0    0     0   144  809  823 100  1  0  0  0
 2  1  82952 432028 160900 1893804    0    0     0   144  931  912 100  1  0  0  0
 2  1  82952 432028 160900 1893824    0    0     0   112 1004  838 100  0  0  0  0
Command terminated by signal 15
0.00user 0.00system 0:09.12elapsed 0%CPU (0avgtext+0avgdata 896maxresident)k
0inputs+576outputs (0major+62minor)pagefaults 0swaps

1つダミープロセスを動かしたらIDLEがなくなり、もう1つ動かしたらI/O WAITもなくなりましたね。そして、/usr/bin/timeに至っては、最初からCPUは0%と言っています。

まとめ

vmstatのiowaitはCPUが全部使われるようになったら見えなくなってしまう。I/OのIN/OUTの量は見えているので、I/Oを気にするならそちらを見よう。

未分類linux

Posted by first_user