缓存 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/。缓存构建通常是三种索引模式中最慢的,因为每个文件都必须解析一次。
构建成功后会输出一行摘要:
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 页面缓存的语法树上的冷查询 |
实际上,事实索引的冷查询速度最快,而缓存的热查询速度最快。选择适合你工作负载的模式,也可以同时构建两者并让工具自行选择。
编程方式使用
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(性能分析中表现为耗时集中在词法分析器和解析器)。
- 你有足够的磁盘空间存储序列化语法树。
如果查询通常只命中代码库的一小部分,而且匹配结果集中在可识别的调用名称或类名附近,那么事实索引通常更合适。如果查询覆盖代码库的范围很广,但你能接受一次性的解析成本,那么缓存更合适。