VirtualBoxのファイルシステムを10倍速くする ~ find編 ~
もう、あって当たり前というところまで浸透してきた仮想環境、みなさまは何をお使いでしょうか?
私の周辺ではVirtualBoxがよく使われています。
典型的な使い方としては、 以下のような感じです。
- ホストOSには、mac/windowsをつかう
- ゲストOSには、Linuxを使う
- 共有フォルダを使って、ホストとゲストでファイルを共有する
その中でも地味に重要なのが共有フォルダ。
共有フォルダとは、ホストOSのファイルシステムをゲストOSからマウントするための、VirtualBoxが提供している仕組みです。
しかし便利な反面、ファイルアクセスが非常に遅いという声をよく聞きます。
findが終わらないとか、git statusが遅すぎるとか...
この問題への対策を探してみると、下記のような物がみつかります。
しかし、vboxsfそのものへの対策は取られていないようです。
そこで、vboxsfと他のファイルシステムとの挙動や実装の違いを調べ、vboxsfを速くしてみようと思いました。
そのいくつか紹介していきたいと思います。
今回取り上げるのは、findが遅い件について。
検証した環境
検証した環境としては以下の通りです。
また、調査対象にしたディレクトリは、ファイル数3万、容量は4GBとなっています。
環境 | ツール | version |
---|---|---|
VirualBox | 4.3.28 | |
VMWare Fusion | 7.1.2 | |
ホストOS | OS X Yosemite 10.10.2 | |
ゲストOS | Debian 8.1 (Jessie) | |
findutils | GNU findutils 4.4.2 | |
coreutils | GNU coreutils 8.23 | |
glibc | 2.19 | |
linux kernel | 3.16.0-4-amd64 | |
VBoxGuestAdditions | 4.3.18 | |
VMwareTools | 9.9.3 |
現象を確認する
まずは、findコマンドが本当に遅いのか、他のファイルシステムと比較してみることにしました。
比較対象には、VMWareのホスト-ゲスト間のファイル共有で使われているvmhgfsを選びました。
これを選んだ理由は、vboxsfと仕組みが良く似ていることからです。
vboxsfとvmhgfsそれぞれで、findコマンド実行したときのtime/straceの結果を下に示します。
timeの結果を見ると、確かにvboxsfの方が遅くなっていることが確認できます。
またstraceで、実行されているシステムコールを確認してみると、vboxsfにおいて最も時間のかかっているシステムコールはnewfstatat
で、その実行回数がvmhgfsと大きく異なる事がわかります。
ここから、vboxsf/vmhgfsの何らかの違いによってfindコマンド内で実行される処理が異なっていることがわかります。
parameter | vmhgfs | vboxsf |
---|---|---|
time(real) | 0m7.205s | 0m19.774s |
time(sys) | 0m5.740s | 0m8.088s |
// vboxsf
$ strace -c find .
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
76.59 0.509083 15 34145 newfstatat <-ここ
10.86 0.072196 73 993 openat
10.24 0.068043 68 1005 6 open
2.14 0.014249 0 34145 write
0.14 0.000952 1 998 fstat
0.03 0.000173 0 2013 getdents
(省略)
// vmhgfs
$ strace -c find .
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
94.99 3.207002 1593 2013 getdents
2.36 0.079565 79 1005 6 open
1.96 0.066032 66 993 openat
0.38 0.012751 13 993 newfstatat <-ここ
0.19 0.006551 0 34145 write
0.12 0.004198 2 1993 close
0.00 0.000139 0 998 fstat
(省略)
コードを追う
つぎに、なぜvboxsf/vmhgfsでnewfstatatの呼び出し回数が異なるのかを調べてみました。
findのコード
コードを読み進めていった結果、newfstatatを実行するか否かは、以下で決められている事がわかりました。
// findutils-4.4.2/gnulib/lib/fts.c 1135-1140
bool skip_stat = (ISSET(FTS_PHYSICAL)
&& ISSET(FTS_NOSTAT)
&& DT_IS_KNOWN(dp)
&& ! DT_MUST_BE(dp, DT_DIR));
p->fts_info = FTS_NSOK;
fts_set_stat_required(p, !skip_stat);
dp
はglibcの関数readdirを使って取得したdirent 構造体です。
dirent 構造体は、ファイルパスとinode番号をキャッシュしているもので、主にパスからinode番号への変換に使われています。
この判定ではd_type
を確認し、DT_UNKNOWN
,DT_DIR
であった場合に、statを実行することになっています。
また、gdbでvboxsf/vmhgfsでこの時の値を確認してみると、vboxsfでは常にdp->d_type=0 (DT_UNKNOWN)
であるに対して、vmhgfsではdp->d_type=4 (DT_DIR)
、dp->d_type=8 (DT_REG)
を返している事がわかりました。
確かに readdirの説明にも以下のように記されています。
d_type フィールドは Linux 以外では 主に BSD 系のシステムにだけ存在する. このフィールドを使うと その後の動作がファイルの種別により決まる場合に lstat(2) を呼び出すコストを避けることができる. 機能検査マクロ BSDSOURCE が定義された場合 glibc は d_type で返される値として以下のマクロ定数を定義する.
つまりこの問題は、vboxsfではd_type
を全てDT_UNKOWN
で返しているために、statを余計に実行する必要があり、その分vmhgfsより遅くなっている事が原因であるといえます。
glibc / linux kernelのコード
続いて、なぜvboxsfでd_type
を全てDT_UNKOWN
で返しているのか調べてみました。
そのために、readdir
がどのようにファイルシステムから情報を取得しているのかを追う必要があります。
readdirは、glibc内に定義されており、以下のようにsyscall getdents
を呼んでいることがわかります。
# glibc-2.19/sysdeps/posix/readdir.c
bytes = __GETDENTS (dirp->fd, dirp->data, maxread);
また、getdents
は、linux kernel内で定義されており、その中からfile->f_op->iterate
が呼び出されています。
// linux/fs/readdir.c
if (!IS_DEADDIR(inode)) {
ctx->pos = file->f_pos;
res = file->f_op->iterate(file, ctx);
file->f_pos = ctx->pos;
fsnotify_access(file);
file_accessed(file);
}
file->f_op
はファイル操作を行うメソッド群で、ファイルシステムごとに定義されています。
vboxsfのコード
file->f_op
はファイルシステムに定義されているので、ここからはvboxsfの実装をみていきます。
vboxsfにおいて、file->f_op->iterate
は以下のように定義されています。
他のファイルシステムでも、適当にgrepすれば簡単に見つけられるでしょう。
// VirtualBox-4.3.28/src/VBox/Additions/linux/sharedfolders/dirops.c
struct file_operations sf_dir_fops =
{
.open = sf_dir_open,
.iterate = sf_dir_iterate,
.release = sf_dir_release,
.read = generic_read_dir,
.llseek = generic_file_llseek
};
そして、vboxsfのsf_dir_iterateを追っていくと、以下の記述が見つかりました。
// VirtualBox-4.3.28/src/VBox/Additions/linux/sharedfolders/dirops.c
if (!dir_emit(ctx, d_name, strlen(d_name), fake_ino, DT_UNKNOWN))
{
LogFunc(("dir_emit failed\n"));
return 0;
}
dir_emit
は、取得したファイル名とinode番号をディレクトリエントリとして、登録するための関数です。
このようにvboxsfでは、getdents
を実行した結果として、inode番号とファイル名だけ返しており、d_type
には全てDT_UNKNOWN
を返していることがわかりました。
そのため、vboxsfでホスト側からdentryの種類を取得し、d_type
として返すことができれば、vboxsfでもfindを速くすることができると考えました。
vboxsfを修正する
正しいd_type
を登録するためには、ホスト側でd_type
を取得しなければなりません。
dentryを取得するやり方を確認すると、ゲスト側で動いているvboxsfから、ホスト側で動いているサービスに結果を問い合わせていることがわかりました。
dentryを取得しているホスト側でのコードはmac/windowsそれぞれ以下のようになっています。
// VirtualBox-4.3.28/src/VBox/Runtime/r3/posix/dir-posix.cpp
RTDECL(int) RTDirRead(PRTDIR pDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry)
...
pDirEntry->INodeId = pDir->Data.d_ino; /* may need #ifdefing later */
pDirEntry->enmType = rtDirType(pDir->Data.d_type);
pDirEntry->cbName = (uint16_t)cchName;
// VirtualBox-4.3.28/src/VBox/Runtime/r3/win/direnum-win.cpp
RTDECL(int) RTDirRead(PRTDIR pDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry)
...
pDir->fDataUnread = false;
pDirEntry->INodeId = 0; /** @todo we can use the fileid here if we must (see GetFileInformationByHandle). */
pDirEntry->enmType = pDir->Data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
? RTDIRENTRYTYPE_DIRECTORY : RTDIRENTRYTYPE_FILE;
pDirEntry->cbName = (uint16_t)cchName;
これを見ると、dentryの情報を取得する際、d_type
(pDirEntry->enmType)も一緒に取得されていることがわかります。
ここから問題は、ゲスト側まで値を返しているけどそれを設定していないだけであるようです。
そこでvboxsfで、こんな感じでホスト側から取得したd_type
を返すように修正を行いました。
結果
この修正を行った上で、先ほどと同様にfindコマンドを実行したときのtime/straceの結果を以下に示します。
以下のように、newfstatat
の呼び出し回数が減っていることがわかります。
また、その分実行時間も短くなっており、vmhgfsと同程度の速度になりました。
parameter | vmhgfs | vboxsf (before) | vboxsf (after) |
---|---|---|---|
time(real) | 0m7.205s | 0m19.774s | 0m3.385s |
time(sys) | 0m5.740s | 0m8.088s | 0m0.860s |
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
55.75 0.090346 90 1005 6 open
37.71 0.061110 62 993 openat
4.58 0.007425 0 34145 write
0.97 0.001571 2 993 newfstatat <- ここ
0.66 0.001071 1 998 fstat
0.20 0.000326 0 1989 fchdir
0.12 0.000194 0 2013 getdents
0.00 0.000000 0 4 read
(省略)
まとめ
vboxsfで正しいd_type
を返してやることで、findのようにd_type
を使って処理を削減しているコマンドを速くすることができました。
またこの修正は、VirtualBox 5.0.2の修正に取り込まれました。