背景
数 10 万~数 100 万ファイルがあるようなディレクトリでうっかり ls
を叩かないためのメモです。
結論
結果を大量に表示する処理が処理時間の大きな部分を占めます。
表示対象を限定するオプションや、コマンドを活用して表示する量を減らせば処理の高速化ができます。
例
ファイルの数を数える
ls -U | wc -l
一覧を見る
# ソートされた一覧
ls | less
# ソートされていない一覧(とにかく早く見たい場合)
ls -U | less
# ソートされていない一覧(さらに早く見たい場合)
find -type f -name '*' | less
ファイルを探す
# 詳しくは man find
find /root/test -type f -regex '.*/dummy-010000.*'
特定のファイルを操作する( -exec
オプションは一度に大量のファイルを操作しようとすると時間がかかるので使わない)
# パスの引数を 1 つだけとる場合
find /root/test -type f -name '*-010000*' | xargs touch
# cp や mv の場合(以下はどれも同じことをしています)
find /root/test -type f -name '*-010000*' | xargs cp --target-directory=/root
find /root/test -type f -name '*-010000*' | xargs -i cp {} /root
find /root/test -type f -name '*-010000*' | xargs -i% cp % /root
測定
以下、試した結果を残しておきます。
テスト環境
- CentOS 6.6
- VMware Player 上の仮想マシン
- SSD 使用
- SSH 接続でコマンド実行
- 以下のコマンドで 100 万ファイル作成
time (for i in `seq -w 1 1000000`; do dd if=/dev/zero of=dummy-$i.dat count=1 bs=1k > /dev/null 2>&1; done)
注意: df -i
で IFree が十分あるか確認してから作成してください。inode が枯渇するとファイルが作成されません。
[root@centos6 test]# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/mapper/vg_centos6-lv_root
1148304 85316 1062988 8% /
tmpfs 238379 1 238378 1% /dev/shm
/dev/sda1 128016 46 127970 1% /boot
[root@centos6 test]# time (for i in `seq -w 1 1000000`; do dd if=/dev/zero of=dummy-$i.dat count=1 bs=1k > /dev/null 2>&1; done)
real 135m16.285s
user 48m24.427s
sys 112m1.841s
[root@centos6 test]# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/mapper/vg_centos6-lv_root
1148304 1085312 62992 95% /
tmpfs 238379 1 238378 1% /dev/shm
/dev/sda1 128016 46 127970 1% /boot
[root@centos6 test]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg_centos6-lv_root
18G 6.8G 9.6G 42% /
tmpfs 932M 0 932M 0% /dev/shm
/dev/sda1 477M 64M 389M 14% /boot
作成に 2 時間 15 分、inode は 95 % 使いました。
キャッシュの削除
ここを参考にキャッシュを削除します。毎回コマンドを打つ前に実行することで、初回実行時と同じ結果を得ることが出来ます。
sync && sysctl -w vm.drop_caches=3
ls
オプションを付けない場合
標準出力に表示した場合、約 40 秒かかります。
[root@centos6 test]# time ls
(略)
dummy-0199998.dat dummy-0399998.dat dummy-0599998.dat dummy-0799998.dat dummy-0999998.dat
dummy-0199999.dat dummy-0399999.dat dummy-0599999.dat dummy-0799999.dat dummy-0999999.dat
dummy-0200000.dat dummy-0400000.dat dummy-0600000.dat dummy-0800000.dat dummy-1000000.dat
real 0m41.782s
user 0m5.526s
sys 0m11.273s
結果をコンソールに表示しなければ約 7 秒で終わります。
[root@centos6 test]# time ls > /dev/null
real 0m6.803s
user 0m3.928s
sys 0m0.607s
これは real
に大きな差が出ていることから ls
で一覧を作る処理以外の部分で時間がかかっているように見えます。出力結果は合計約 18 MBあり、結果を大量に表示する処理に時間がかかっていると考えられます。ネットワーク帯域がボトルネックになる可能性もありますが、今回は 18 MB のファイル転送に 1 秒程度しかかからないローカル上の仮想マシンでテストしているので無視できるものとします。1
[root@centos6 test]# ls > ~/ls.txt
[root@centos6 test]# ls -alh ~/ls.txt
-rw-r--r--. 1 root root 18M 5月 30 12:55 2015 /root/ls.txt
また、単純に ls
をした場合は色付けや表示の整形がされるため、さらに表示コストがかさみます。一度テキストに落としたものを cat
で表示すると約 11 秒でした。
[root@centos6 test]# time cat ~/ls.txt
(略)
dummy-0999998.dat
dummy-0999999.dat
dummy-1000000.dat
real 0m10.630s
user 0m0.000s
sys 0m0.441s
ソートしないで高速化
-f
または -U
オプションでソートせずに高速に表示できます。-f
はドットファイルを表示しますが -U
は表示しません。
[root@centos6 test]# ls -f temp1 | head -5
.
..
dummy-379969.dat
dummy-223689.dat
dummy-347368.dat
[root@centos6 test]# ls -U temp1 | head -5
dummy-379969.dat
dummy-223689.dat
dummy-347368.dat
dummy-90543.dat
dummy-227966.dat
ソート処理自体にも数秒かかるため、ソートしなければ高速化できます。
[root@centos6 test]# time ls -U > /dev/null
real 0m3.143s
user 0m0.030s
sys 0m0.677s
[root@centos6 test]# time ls > /dev/null
real 0m6.923s
user 0m3.951s
sys 0m0.607s
ソートせずに -1
オプションで 1 行 1 ファイルずつ表示すると約 28 秒でした。
[root@centos6 test]# time ls -U1
(略)
dummy-0508749.dat
dummy-0928884.dat
dummy-0954666.dat
real 0m28.069s
user 0m2.063s
sys 0m18.639s
まとめ
まとめると、 ls
の処理にはそれぞれ以下の時間がかかっていると考えられます(数字はざっくりです)。
処理内容 | 処理時間(秒) | 全体に占める割合(%) | 回避方法 |
---|---|---|---|
ファイルリストの作成 | 3 | 7 % | なし |
ファイルのソート | 4 | 10 % |
-U , -f オプション |
出力の整形 | 23 | 56 % | 別コマンドへパイプ、画面にそのまま出力しない |
ネットワークの経由 | 0 | 0 % | 画面表示量の抑制 |
画面への出力 | 11 | 27 % | 画面表示量の抑制 |
合計 | 41 | 100 % | - |
find
ファイルの検索
find
は検索条件に一致したファイルを順次標準出力します。速さは約 20 秒です。また、less
と組み合わせると処理の途中でも順次結果が表示されるため、すぐに内容を見ることができます。
[root@centos6 test]# time (find -type f -name '*')
./dummy-0508749.dat
./dummy-0928884.dat
./dummy-0954666.dat
real 0m19.511s
user 0m1.654s
sys 0m14.049s
コマンドの実行
-exec
コマンドで一致したファイルに対してコマンドを実行することができますが、大量のファイルが対象となる場合、 1 ファイルごとにコマンドが実行されるため遅くなります。こうした場合には xargs
に渡すことで引数の長さを考慮してまとめて実行してくれます。
[root@centos6 test]# time (find . -type f -name '*-011*' -exec cp {} --target-directory=/root/test2 \;)
real 0m51.884s
user 0m1.349s
sys 0m42.798s
[root@centos6 test]# time (find . -type f -name '*-011*' | xargs cp --target-directory=/root/test2)
real 0m4.090s
user 0m0.369s
sys 0m1.486s
まとめ
find
は条件に一致するファイルの表示ができるため、画面表示量の抑制でき、処理時間の短縮が見込めます。また、条件に一致するファイルを xargs
に渡すことでまとめて処理が行えます。
-
ネットワーク帯域が 1 Mbps 程度しかない環境では転送自体に数分かかるため、これがボトルネックになる可能性もありますが、その場合でも画面表示量の抑制は対策として有効です。 ↩