コンテンツへスキップ

ASTキャッシュ検索

ASTキャッシュ検索は、ファクトインデックスを補完する事前作成済みキャッシュモードです。抽出したファクトを保存して一致ファイルを再解析する代わりに、解析済みツリーをシリアライズしてディスクに保存します。以降のクエリではツリーをデシリアライズしてマッチャーを直接実行するため、PHP-Parserの処理全体を省略できます。

キャッシュはgreph-index ast-cache CLIサブコマンドとGreph::buildAstCache() / Greph::searchAstCached()ファサードメソッドから利用できます。複数インデックスやマニフェストを使ったセット検索にも対応しています。

キャッシュの作成

シェル
# Full build at the current directory
./vendor/bin/greph-index ast-cache build .

# Use a non-default cache directory
./vendor/bin/greph-index ast-cache build . --index-dir /tmp/greph-ast-cache

# Build a cache that may refresh automatically for small changes
./vendor/bin/greph-index ast-cache build . \
  --lifecycle opportunistic-refresh \
  --auto-refresh-max-files 32 \
  --auto-refresh-max-bytes 1048576

デフォルトでは、キャッシュは<root>/.greph-ast-cache/に保存されます。キャッシュ作成では、すべてのファイルを一度解析するため、通常3種類のインデックスモードの中で最も時間がかかります。

作成に成功すると、概要が1行で表示されます:

Text
Built AST cache for 2547 files in .greph-ast-cache (2541 cached trees, +2541 ~0 -0 =0)

解析に失敗して(エラーを表示せずに)スキップされたファイルはキャッシュに登録されないため、キャッシュ済みツリーの数は通常、ファイル数よりやや少なくなります。解析エラーを表示するには、検索コマンドに--strict-parseを渡してください。

キャッシュの更新

シェル
./vendor/bin/greph-index ast-cache refresh .

refreshはインデックス対象のルートを再走査し、変更されたファイルを再解析して、変更があったキャッシュ済みツリーのみを書き換えます。

ライフサイクルプロファイルは、ほかの事前作成済みモードと同じです:

  • static: 鮮度の確認も自動変更も行わない
  • manual-refresh: 古い状態をstatsで報告するが、検索中は更新しない
  • opportunistic-refresh: 変更対象が設定済みのしきい値を下回る場合に限り、検索中に更新する
  • strict-stale-check: 更新せず、古いキャッシュでの検索を拒否する

キャッシュの検索

シェル
./vendor/bin/greph-index ast-cache search 'new $CLASS()' src
./vendor/bin/greph-index ast-cache search '$obj->$method($$$ARGS)' src
./vendor/bin/greph-index ast-cache search --json 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-cache search --fallback scan 'new $CLASS()' src
./vendor/bin/greph-index ast-cache search --trace-plan 'array($$$ITEMS)' src

フラグはast-index searchと同じです。キャッシュがない場合の--fallback scanも使用できます。

--index-dirを繰り返し指定すると、1回の呼び出しで複数の事前作成済みキャッシュを検索できます:

シェル
./vendor/bin/greph-index ast-cache search \
  --index-dir ../wordpress/.greph-ast-cache \
  --index-dir ./.greph-ast-cache \
  --show-index-origin \
  'array($$$ITEMS)' .

マニフェストを使ったキャッシュセット

名前付きの事前作成済みセットがある場合、同じマニフェストでASTキャッシュ検索を実行できます:

シェル
./vendor/bin/greph-index set build --mode ast-cache
./vendor/bin/greph-index set stats --mode ast-cache --dry-refresh
./vendor/bin/greph-index set search --mode ast-cache --show-index-origin 'array($$$ITEMS)' .

プランナーの診断

--trace-planはASTキャッシュのプランナーによる判断をstderrに報告します。次の情報が含まれます:

  • 検索した事前作成済みキャッシュ
  • 古いキャッシュの更新処理が実行されたかどうか
  • ファイル単位の絞り込み後に残った候補ファイル数
  • 正確な構造マッチングのためにデシリアライズしたキャッシュ済みツリーの数

キャッシュとファクトインデックス

どちらのモードもAST検索を高速化しますが、対象とする処理はそれぞれ異なります。

項目 事実インデックス (ast-index) キャッシュ (ast-cache)
保存内容 抽出した事実(呼び出し名、クラス名、宣言) シリアライズされた解析済みツリー
構築コスト 低い 高い(すべてを解析)
構築サイズ 小さい 大きい
クエリ経路 事実で絞り込み、候補を解析して照合 ツリーをデシリアライズして照合
最適な用途 識別しやすい先頭部分(呼び出し名、クラス名)があるパターン 識別要素が名前ではなく構造であるパターン
不向きな用途 先頭部分がメタ変数であるパターン OSのページキャッシュに収まらなくなったツリーへのコールドクエリ

実際には、コールドクエリは事実インデックスが最速で、ウォームクエリはキャッシュが最速です。ワークロードに合う方を使うか、両方を構築してツールに選ばせてください。

プログラムからの利用

PHP
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
use Greph\Index\IndexLifecycleProfile;

Greph::buildAstCache('.', lifecycle: IndexLifecycleProfile::Static);

$matches = Greph::searchAstCached(
    'array($$$ITEMS)',
    'src',
    new AstSearchOptions(jobs: 4, tracePlan: true),
);

searchAstCachedはネイティブAST検索と同じAstSearchOptionsを受け取り、同じ形式のlist<AstMatch>を返します。異なるのは、キャッシュ済みのファイルではパーサーパイプラインを省略する点だけです。複数インデックスやマニフェストを使うフローでは、searchAstCachedMany(...)とsearchAstCachedSet(...)を使ってください。

使用する場面

キャッシュ済みASTモードが適しているのは、次の場合です。

  • 同じコードベースに対して構造クエリを繰り返し実行する。
  • 以前の実行でボトルネックになっていたのがPHP-Parser(プロファイルで字句解析と構文解析にかかった時間として確認できる)である。
  • シリアライズされたツリーストア用のディスク容量を確保できる。

クエリが通常コードベースの一部だけに当たり、マッチが識別しやすい呼び出し名やクラス名に集中するなら、事実インデックスの方が適しています。コードベース全体を広く検索するものの、初回の解析コストを許容できるなら、キャッシュが有利です。