Twig渲染慢的根源是编译、继承和变量求值三步:缓存未生效导致重复编译,模板嵌套过深(超5层)增加解析开销,模板内执行过滤器链或深层对象访问引发运行时计算。

WebProfiler 里看到 Twig 渲染耗时高,不是模板写得“慢”,而是编译、继承、变量求值这三步在悄悄拖后腿。
看 Profiler 里的 Twig 标签页到底在报什么
打开任意页面底部的 Symfony 工具栏 → 点 Profiler → 切到 Twig 标签页,重点盯三项:
-
render time:单个模板实际执行时间(含 include、embed、过滤器链) -
compilation time:该模板首次加载时编译成 PHP 的耗时(只在 cache 失效时出现) -
template hierarchy:列出所有被加载的模板及继承/包含关系,一眼看出是否嵌套过深
如果 compilation time 非零且反复出现,说明缓存没生效;如果 template hierarchy 显示 5 层以上 {% extends %} 或大量 {% include %},渲染栈已开始吃 CPU。
cache 配置错一个字母,Twig 就退化成解释器
生产环境禁用缓存等于让 Twig 每次都重解析 .twig 文件——这不是“慢”,是直接放弃性能底线。
-
cache必须指向真实可写的绝对路径,比如/var/cache/twig,不能是相对路径或__DIR__.'/cache' -
auto_reload在生产环境必须为false;设成true会让 Twig 每次都检查文件修改时间,开销不比编译小 -
debug必须为false:开启后 Twig 会注入额外调试信息、禁用部分优化,且auto_reload强制生效 - 确认目录权限:Web 进程用户(如 www-data)必须对
cache目录有rwx权限,否则静默失败,降级为无缓存模式
模板里写 {{ user.posts|reverse|slice(0, 5) }} 是在请求现场跑算法
Twig 不会帮你把数据预处理好——它只负责把表达式当 PHP 代码执行。长过滤器链、深层属性访问、循环内 include,全在渲染时同步发生。
- 避免在模板中做排序、分页、过滤:这些逻辑应由控制器或服务层完成,传入已处理好的数组
- 慎用
{{ user.profile.settings.theme }}:对象链访问触发多次__get()和魔法方法,换成{{ theme }}直接传值 -
{% for %}内不要嵌{% include %}:每次迭代都重新加载、解析、编译子模板,O(n) 变成 O(n²) - 用
{% cache %}包裹静态区块:比如页脚、导航菜单,但注意 key 要带动态标识(如'footer_' ~ app.request.locale)
Profiler 自己也可能是瓶颈源
开发环境开着 Profiler 查 Twig 性能,本身就会增加 10–30ms 开销,尤其当启用了 debug + auto_reload 时,compilation time 数值已失真。
真正要定位问题,得关掉 Profiler 后用 $twig->profile() 手动打点:
$profile = $twig->profile($template);
$twig->render('page.html.twig', $data);
$dumper = new \Twig\Profiler\Dumper\TextDumper();
echo $dumper->dump($profile);
这个输出会精确到每个 {% block %}、每个 |filter 的耗时,比 Profiler 更接近真实场景。别忘了——线上环境永远关 Profiler,靠 intaro/pinba-bundle 这类 UDP 上报方案做持续观测。



















