跳转到正文

缓存 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/。缓存构建通常是三种索引模式中最慢的,因为每个文件都必须解析一次。

构建成功后会输出一行摘要:

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,即可在一次调用中搜索多个预热缓存:

命令行
./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(性能分析中表现为耗时集中在词法分析器和解析器)。
  • 你有足够的磁盘空间存储序列化语法树。

如果查询通常只命中代码库的一小部分,而且匹配结果集中在可识别的调用名称或类名附近,那么事实索引通常更合适。如果查询覆盖代码库的范围很广,但你能接受一次性的解析成本,那么缓存更合适。