大量のファイルがあるディレクトリでのコマンドメモ

# 背景

数 10 万～数 100 万ファイルがあるようなディレクトリでうっかり `ls` を叩かないためのメモです。

# 結論

結果を大量に表示する処理が処理時間の大きな部分を占めます。
表示対象を限定するオプションや、コマンドを活用して表示する量を減らせば処理の高速化ができます。

## 例

ファイルの数を数える

```bash
ls -U | wc -l
```

一覧を見る

```bash
# ソートされた一覧
ls | less
# ソートされていない一覧（とにかく早く見たい場合）
ls -U | less
# ソートされていない一覧（さらに早く見たい場合）
find -type f -name '*' | less
```

ファイルを探す

```bash
# 詳しくは man find
find /root/test -type f -regex '.*/dummy-010000.*'
```

特定のファイルを操作する（ `-exec` オプションは一度に大量のファイルを操作しようとすると時間がかかるので使わない）

```bash
# パスの引数を 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 万ファイル作成

```bash
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 % 使いました。

## キャッシュの削除

[ここ](http://www.shift-the-oracle.com/linux/utility/flush-buffer-cache.html)を参考にキャッシュを削除します。毎回コマンドを打つ前に実行することで、初回実行時と同じ結果を得ることが出来ます。

```bash
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]: ネットワーク帯域が 1 Mbps 程度しかない環境では転送自体に数分かかるため、これがボトルネックになる可能性もありますが、その場合でも画面表示量の抑制は対策として有効です。