ディレクトリの中にある大量の小さなファイルを高速に読み込む方法 - 射撃しつつ前転を読んで、なるほど、と思ったのでGoで実装してみる。

io/ioutilにあるReadDirはディレクトリ内にあるエントリを名前順にソートしているので、inode順にソートするような関数を自前で作る。

type byInode []os.FileInfo

func (this byInode) Len() int { return len(this) }
func (this byInode) Less(i, j int) bool {
    fstat_i := this[i].Sys().(*syscall.Stat_t)
    fstat_j := this[j].Sys().(*syscall.Stat_t)
    return fstat_i.Ino < fstat_j.Ino
}
func (this byInode) Swap(i, j int) { this[i], this[j] = this[j], this[i] }

func MyReadDir(dirname string) ([]os.FileInfo, error) {
    f, err := os.Open(dirname)
    if err != nil {
        return nil, err
    }
    list, err := f.Readdir(-1)
    f.Close()
    if err != nil {
        return nil, err
    }
    return list, nil
}

func MyReadDirSortByInode(dirname string) ([]os.FileInfo, error) {
    f, err := os.Open(dirname)
    if err != nil {
        return nil, err
    }
    list, err := f.Readdir(-1)
    f.Close()
    if err != nil {
        return nil, err
    }
    sort.Sort(byInode(list))
return list, nil
}

ファイル数10,000、平均ファイルサイズ112KBに対して、上記のMyReadDirと、io/ioutilのReadDirを比較すると、次のようになった。 ファイルシステムはext4を使用している。

sudo sysctl -w vm.drop_caches=3
time go run dirdump.go dir 1
go run dirdump.go large 1  4.07s user 4.54s system 19% cpu 43.450 total

sudo sysctl -w vm.drop_caches=3
time go run dirdump.go dir 2
go run dirdump.go large 2  5.85s user 8.98s system 4% cpu 4:58.27 total

ソースコードはgistにアップロードした。 上記のコマンドは、dirというディレクトリに入っているファイルの数と、行数の合計値を数える。1と指定すると、inode順に、2と指定するとソートをしない。

ext4でも、ソートしないよりも、inode順にソートするほうが高速にファイルを読み込めそうである。


関連記事


最近の記事