require, loadの高速化を行うbootsnap(1.2.0)のコードリーディングをしました。今回はrequireのパス検索の高速化部分を読んでいきます。

railsではboot/setupをrequireしますが、最終的にBootsnap.setupが呼ばれるのでここからコードを読んでいきます。

Bootsnap.setupの定義は以下の通りで、requireのパス検索を高速化するのはBootsnap::LoadPathCache.setupのところになります。

Bootsnap::LoadPathCache.setupはload_pach_cache/core_ext/kernel_require.rbをrequireします。以下、通常のrequire, loadのパターンを追っていきます(今回はActiveSupportのautoloadは追いません)

load_pach_cache/core_ext/kernel_require.rbでは以下のようにKernelに対してモンキーパッチしており、requireやloadメソッドが新しいメソッドに置き換えられます。LoadPathCache.load_path_cache.findで指定した文字列に対して読み込むファイルの絶対パスを返してresolved変数にセットし、そのresolvedを引数に元のrequireを呼び出しています。絶対パスで指定した場合は$LOAD_PATHを辿らないため高速に読み込めるそうです。

LoadPathCache.load_path_cacheはBootsnap::LoadPathCache::Cacheのインスタンスです。以下のようにコンストラクタで#reinitializeを呼び出します。

#reinitializeはpush_paths_lockedで@indexと@dirsをセットします。

pathsは$LOAD_PATHになります。$LOAD_PATHからファイルとディレクトリの相対パスを取り出して@dirsと@indexをセットしています。@indexはファイル名をキーに$LOAD_PATHの絶対パスを値にしたハッシュになります。

Cache#findは以下のように定義されています。

search_indexは@indexにファイル名のキーが存在すれば絶対パス化した文字列を返しています。

また、各ファイルのエントリーとディレクトリはキャッシュしています。store.setでハッシュに格納しています。

このハッシュ値はBootsnap::LoadPathCache::Store#dump_data(#transaction経由)によってMessagePack形式でファイルとして格納されます。これが tmp/cache/bootsnap-load-path-cacheのファイルになります。

Bootsnap::LoadPathCache::Path#entries_and_dirsメソッド内で#stable?によって分岐しているところですが、gemファイルなどの#stable? = trueなファイルは変更がないはずなのでentries, dirsを半永続的にキャッシュし、そうでなければmtimeが異なっていればキャッシュを使わずに再度ディレクトリを操作してentries, dirsを取得しています。