今日(2023/12/15)現在、Bunのバージョンは v1.0.18まで行ってますが(速いっ)、遅ればせながら2023/12/2 にリリースされた Bun v1.0.15 を眺めていたら、
「recursiveはfs.readdir()Node.js より 40 倍高速です」
と書いてあったので確かめてみました。
recursive (リカーシブ) は「再帰的」という意味なので、まぁそういう機能ですね。下のディレクトリまで順にみに行ってくれます。
Bunでは、v1.0.15 からですが、Node.js ではリファレンスによると v20.1.0, v18.17.0 と2つ書かれています。(Node.js の方は見ていなかったので詳細は判らないです。)
まずディレクトリをサーチするので、その前に mydir ディレクトリを作り、その下にdir1からdir100までのサブディレクトリを用意して、それをサーチする速度を比較することにします。
まず、mydir1/mydir2/ディレクトリを作りそこへ更にディレクトリを100個シェルで作ります。
mkdir mydir1
cd mydir1
mkdir mydir2
cd mydir2
for i in {1..100}; do
mkdir "dir$i"
done
mydir1 の下はこんな感じになります
$ tree
.
└─ mydir1/
└─ mydir2/
├─ dir1/
├─ dir2/
├─ dir3/
省略
└─ dir100/
では、コードをbun用とnode用で2つ作ります。普通は1つで良いのですが、今回は、ES Moduleモードを使ってなるべく同じ感じのコードで比較したいという事で、Node.jsでも ES Moduleが動く 拡張子 .mjs を使いました。 .mjs は bun でも動きます。(Bunは.js, .ts, .cjs, .mjs, .jsx, .tsxファイルを直接実行できます。あー楽ちん。)
まず、recursive設定の無い普通のコード。
console.time('readdir')
import { readdir } from "fs/promises";
const results = await readdir('/home/tato/bun/readdir/mydir1');
console.log('count: ', results.length);
console.log(results); // ["a.js", "b/c.js" ...
console.timeEnd('readdir')
次に、{ recursive: true } 設定のあるコード。
'<ディレクトリへのパス>'の部分は、__dirnameがCommon JSでしかサポートされておらず、ES Moduleだと別途モジュール読み込まないといけないという事で手を抜きました。はい。bunならimport と __dirname まぜても使えるのになぁ。
console.time('readdir')
import { readdir } from "fs/promises";
const results = await readdir('<ディレクトリへのパス>', { recursive: true });
console.log('count: ', results.length);
console.log(results); // ["a.js", "b/c.js" ...
console.timeEnd('readdir')
では結果を見てみましょう。まずは、Node.jsから。
$ node readdir-recursive-true.mjs #有り
[
'mydir2', 'mydir2/...', 'mydir2/dir1', 'mydir2/dir10',
'mydir2/dir100', 'mydir2/dir11', 'mydir2/dir12', 'mydir2/dir13',
'mydir2/dir14', 'mydir2/dir15', 'mydir2/dir16', 'mydir2/dir17',
'mydir2/dir18', 'mydir2/dir19', 'mydir2/dir2', 'mydir2/dir20',
'mydir2/dir21', 'mydir2/dir22', 'mydir2/dir23', 'mydir2/dir24',
'mydir2/dir25', 'mydir2/dir26', 'mydir2/dir27', 'mydir2/dir28',
'mydir2/dir29', 'mydir2/dir3', 'mydir2/dir30', 'mydir2/dir31',
'mydir2/dir32', 'mydir2/dir33', 'mydir2/dir34', 'mydir2/dir35',
'mydir2/dir36', 'mydir2/dir37', 'mydir2/dir38', 'mydir2/dir39',
'mydir2/dir4', 'mydir2/dir40', 'mydir2/dir41', 'mydir2/dir42',
'mydir2/dir43', 'mydir2/dir44', 'mydir2/dir45', 'mydir2/dir46',
'mydir2/dir47', 'mydir2/dir48', 'mydir2/dir49', 'mydir2/dir5',
'mydir2/dir50', 'mydir2/dir51', 'mydir2/dir52', 'mydir2/dir53',
'mydir2/dir54', 'mydir2/dir55', 'mydir2/dir56', 'mydir2/dir57',
'mydir2/dir58', 'mydir2/dir59', 'mydir2/dir6', 'mydir2/dir60',
'mydir2/dir61', 'mydir2/dir62', 'mydir2/dir63', 'mydir2/dir64',
'mydir2/dir65', 'mydir2/dir66', 'mydir2/dir67', 'mydir2/dir68',
'mydir2/dir69', 'mydir2/dir7', 'mydir2/dir70', 'mydir2/dir71',
'mydir2/dir72', 'mydir2/dir73', 'mydir2/dir74', 'mydir2/dir75',
'mydir2/dir76', 'mydir2/dir77', 'mydir2/dir78', 'mydir2/dir79',
'mydir2/dir8', 'mydir2/dir80', 'mydir2/dir81', 'mydir2/dir82',
'mydir2/dir83', 'mydir2/dir84', 'mydir2/dir85', 'mydir2/dir86',
'mydir2/dir87', 'mydir2/dir88', 'mydir2/dir89', 'mydir2/dir9',
'mydir2/dir90', 'mydir2/dir91', 'mydir2/dir92', 'mydir2/dir93',
'mydir2/dir94', 'mydir2/dir95', 'mydir2/dir96', 'mydir2/dir97',
... 2 more items
]
readdir: 20.435ms
$ node readdir-recursive-none.mjs #無し
count: 1
[ 'mydir2' ]
readdir: 10.321ms
{ recursive: true }では簡単に再帰的に下のディレクトリまで拾っていますね。
次に、Bunでやってみます。
$ bun readdir-recursive-true.mjs #有り
[
"mydir2", "mydir2/dir91", "mydir2/dir58", "mydir2/dir87", "mydir2/dir39", "mydir2/dir92", "mydir2/dir63",
"mydir2/dir42", "mydir2/dir62", "mydir2/dir79", "mydir2/dir75", "mydir2/dir22", "mydir2/dir88",
"mydir2/dir44", "mydir2/dir89", "mydir2/...", "mydir2/dir64", "mydir2/dir19", "mydir2/dir97",
"mydir2/dir41", "mydir2/dir26", "mydir2/dir95", "mydir2/dir2", "mydir2/dir69", "mydir2/dir8",
"mydir2/dir73", "mydir2/dir52", "mydir2/dir53", "mydir2/dir83", "mydir2/dir36", "mydir2/dir33",
"mydir2/dir54", "mydir2/dir93", "mydir2/dir71", "mydir2/dir67", "mydir2/dir84", "mydir2/dir56",
"mydir2/dir21", "mydir2/dir90", "mydir2/dir48", "mydir2/dir70", "mydir2/dir96", "mydir2/dir4",
"mydir2/dir74", "mydir2/dir81", "mydir2/dir7", "mydir2/dir15", "mydir2/dir82", "mydir2/dir25",
"mydir2/dir99", "mydir2/dir23", "mydir2/dir43", "mydir2/dir31", "mydir2/dir38", "mydir2/dir9",
"mydir2/dir10", "mydir2/dir12", "mydir2/dir45", "mydir2/dir40", "mydir2/dir86", "mydir2/dir55",
"mydir2/dir17", "mydir2/dir60", "mydir2/dir6", "mydir2/dir27", "mydir2/dir29", "mydir2/dir13",
"mydir2/dir66", "mydir2/dir85", "mydir2/dir47", "mydir2/dir65", "mydir2/dir18", "mydir2/dir78",
"mydir2/dir80", "mydir2/dir34", "mydir2/dir16", "mydir2/dir3", "mydir2/dir50", "mydir2/dir14",
"mydir2/dir28", "mydir2/dir98", "mydir2/dir61", "mydir2/dir32", "mydir2/dir24", "mydir2/dir5",
"mydir2/dir46", "mydir2/dir37", "mydir2/dir72", "mydir2/dir51", "mydir2/dir94", "mydir2/dir76",
"mydir2/dir1", "mydir2/dir35", "mydir2/dir59", "mydir2/dir77", "mydir2/dir68", "mydir2/dir100",
"mydir2/dir30", "mydir2/dir11", "mydir2/dir20", "mydir2/dir57", "mydir2/dir49"
]
[1.37ms] readdir
$ bun readdir-recursive-none.mjs #無し
count: 1
[ "mydir2" ]
[0.67ms] readdir
このベンチでは、Bunの方が40倍まではいかなかったけど、それでもやはり15倍くらい爆速でした。めでたし。
最近 Qiita に書いた Bun 関連の記事10選