原文:Performance;作者:Symfony 文档贡献者。中文全文译写与编者审核:未完纪。核对日期:2026-10-05。
2026-10-05 抓取的 current 页面版本选择器明确为 Symfony 8.1,源码编辑链接也指向8.1分支;官方8.1安装指南已核对要求PHP 8.4或更高版本。下列php.ini数值是文档起点,需按实际PHP构建、文件数量和内存容量调整。

Symfony 本身注重性能,但服务器配置和应用结构仍会影响最终响应。本篇先按官方清单检查可重复的部署配置,再介绍如何把业务代码的耗时与内存纳入分析器。配置优化负责减少可避免的开销,分析工具则帮助判断真正的瓶颈在哪里。
一、性能检查清单
应用侧首先限制启用的locale数量;生产服务器侧检查容器编译、OPcache、时间戳验证、realpath缓存、Composer自动加载和deepclone扩展。下面分别说明配置方式与适用条件。
只启用应用实际使用的locale
通过 framework.enabled_locales 限制应用支持的locale,只生成实际需要的翻译文件。多语言范围应由业务需求决定,不必为应用用不到的语言额外生成产物。
把服务容器编译到一个文件
Symfony默认把服务容器编译成多个小文件。设置 .container.dumper.inline_factories 为true,可把整个容器编译到单个文件;与PHP类预加载结合时,可能改善性能。下面保留YAML与PHP两种等价配置。
config/services.yaml:合并容器
# config/services.yaml
parameters:
# ...
.container.dumper.inline_factories: true
config/services.php:相同配置的PHP写法
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'parameters' => [
'.container.dumper.inline_factories' => true,
],
]);
参数名开头的点表示该参数只在容器编译过程中使用。它不是一个需要传进业务服务的普通运行时参数。
使用OPcache字节码缓存
OPcache保存PHP脚本编译后的字节码,避免每次请求重新编译。PHP自带OPcache,但在某些安装方式中仍需明确启用。启用后,还要确认真正处理Web请求的PHP进程使用了相应配置。
用OPcache预加载类
OPcache可以在进程启动时编译并加载类,让它们供后续请求使用,直到服务器重启。Symfony在编译容器时,例如执行cache:clear期间,会在var/cache下生成待预加载的类列表。原文建议不要直接引用该缓存文件,而使用Symfony Flex项目生成的config/preload.php入口。
php.ini:预加载入口与用户
; php.ini
opcache.preload=/path/to/project/config/preload.php
; required for opcache.preload:
opcache.preload_user=www-data
若项目缺少该入口文件,可以通过 composer recipes:update symfony/framework-bundle 更新Flex recipe。可以用 container.preload 与 container.no_preload 服务标签控制哪些类应被预加载、哪些应排除。
给OPcache足够的容量
原文认为默认OPcache配置通常不适合Symfony应用,建议从以下数值开始调整:编译文件缓存内存256MB、最多32531个文件、interned strings缓冲32MB。Symfony使用大量完整类名,默认8MB字符串缓冲可能偏小。
php.ini:OPcache容量建议
; php.ini
; maximum memory that OPcache can use to store compiled PHP files
opcache.memory_consumption=256
; maximum number of files that can be stored in the cache
opcache.max_accelerated_files=32531
; memory (in MB) for interned strings; the default value (8 MB) is too low
; for Symfony applications, which use many fully-qualified class names
opcache.interned_strings_buffer=32
这些数值是调优起点。实际应结合应用文件数量、命中情况和服务器可用内存判断,不能把增加缓存容量视为总能加速的无条件操作。
生产环境关闭PHP文件时间戳检查
生产服务器的PHP文件通常只在发布新版本时变化。OPcache默认检查已缓存文件是否修改,这会产生开销。若发布机制能保证在更新后可靠刷新缓存,可以关闭检查:
php.ini:关闭自动时间戳验证
; php.ini
opcache.validate_timestamps=0
关闭后,每次部署必须清空并重新建立OPcache,否则新代码可能不会生效。PHP CLI和Web进程不共享同一个OPcache,因此在终端中随便运行一条PHP命令,无法清理Web服务正在使用的缓存。原文列出三条路线:
- 重启实际提供请求的Web服务或相应PHP工作进程,确保其缓存重建。
- 通过Web进程执行opcache_reset(),让清理作用于正确的缓存。
- 使用cachetool,从CLI通过适当接口控制目标OPcache。
配置PHP realpath缓存
PHP把相对路径解析为实际绝对路径时会缓存结果。Symfony会打开较多PHP文件,原文建议至少使用4MB缓存,并让结果保存600秒:
php.ini:realpath缓存
; php.ini
; maximum memory allocated to store the results
realpath_cache_size=4096K
; save the results for 10 minutes (600 seconds)
realpath_cache_ttl=600
启用open_basedir时,PHP会禁用realpath缓存。这个限制需要在评估性能时考虑,但不应仅为获得缓存收益就删除原有安全约束。
优化Composer自动加载器
开发环境的类加载器要善于寻找新增或变化的类;生产环境则通常仅在部署时更新文件。因此可以预先扫描应用,构建类名到文件位置的映射,保存到vendor/composer/autoload_classmap.php,并把生成过程纳入发布流程。
生成生产class map的原文命令;开头$表示终端提示符
$ composer dump-autoload --no-dev --classmap-authoritative
--no-dev:排除仅开发环境需要的类,包括require-dev依赖与autoload-dev规则。--classmap-authoritative:为应用使用的PSR-0/PSR-4类建立映射;若类不在映射中,就不继续扫描文件系统寻找。
安装deepclone扩展
Symfony使用deepclone扩展的函数来还原缓存池PHP文件中保存的对象,并在编译时复制服务容器。没有扩展时,symfony/polyfill-deepclone提供同样功能的纯PHP实现,原文指出它会慢数倍。可通过PIE在生产与开发机器安装扩展,开发时也能改善容器重建速度。
原文PIE安装命令;仅作静态展示
$ pie install symfony/deepclone
扩展版本、PHP ABI与操作系统需匹配。原文没有在本页提供通用的速度保证,是否值得安装仍应以应用工作负载验证。
按需关闭debug模式的容器XML输出
在debug模式,Symfony生成包含全部服务容器信息的XML,供debug:container、debug:autowiring等命令使用。容器越大,生成文件的时间和体积越大。如果调试收益抵不过重建成本,可以关闭输出;代价是依赖该文件的调试信息会受到影响。
YAML:关闭容器XML输出
# config/services.yaml
parameters:
# ...
debug.container.dump: false
PHP:同一选项
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'parameters' => [
'debug.container.dump' => false,
],
]);
二、分析Symfony应用性能
Blackfire:可选的商业性能分析服务
原文推荐Blackfire用于开发、测试和生产环境的Symfony性能分析。它是商业服务,并提供功能演示。使用它不是后面Stopwatch方案的前提;本篇没有连接服务或向外发送应用数据。
Stopwatch:把业务事件加入Symfony分析器
Symfony开发配置提供基础性能分析器。点击Web调试工具栏的时间面板,可以看到数据库查询、模板渲染等阶段的耗时。Stopwatch组件可以测量自己的代码执行时间和内存,再把结果显示到同一个分析器。
使用自动装配时,在控制器或服务参数上声明Stopwatch类型,Symfony会注入debug.stopwatch服务。下面的DataExporter为导出逻辑建立export-data事件。
业务服务中开始和结束Stopwatch事件
use Symfony\Component\Stopwatch\Stopwatch;
class DataExporter
{
public function __construct(
private Stopwatch $stopwatch,
) {
}
public function export(): void
{
// the argument is the name of the "profiling event"
$this->stopwatch->start('export-data');
// ...do things to export data...
// reset the stopwatch to delete all the data measured so far
// $this->stopwatch->reset();
$this->stopwatch->stop('export-data');
}
}
请求调用这个服务后,分析器会显示名为export-data的新事件。示例中的reset会清掉此前收集的数据,只有确实希望丢弃旧测量时才调用。
start、stop和getEvent都会返回StopwatchEvent对象,事件尚在运行时也能读取其信息。转换为字符串可获得简洁摘要:
输出事件摘要;原文4.50 MiB / 26 ms仅为演示值
// ...
dump((string) $this->stopwatch->getEvent('export-data')); // dumps e.g. '4.50 MiB - 26 ms'
模板也可以借助stopwatch Twig标签测量代码块:
测量博客列表模板渲染
{% stopwatch 'render-blog-posts' %}
{% for post in blog_posts %}
{# ... #}
{% endfor %}
{% endstopwatch %}
用类别组织事件
start的第二个可选参数是类别或标签,便于把同类事件分组。例如把export-data归入export:
给事件指定类别
$this->stopwatch->start('export-data', 'export');
用lap测量每个分段
实体秒表除了开始、停止,还能记圈。Stopwatch的lap会立即停止再重新开始事件,形成连续的period。下例在处理每条记录后记一次lap,最后结束整个事件。
批处理中的分段计时
$this->stopwatch->start('process-data-records', 'export');
foreach ($records as $record) {
// ... some code goes here
$this->stopwatch->lap('process-data-records');
}
$event = $this->stopwatch->stop('process-data-records');
// $event->getDuration(), $event->getMemory(), etc.
// Lap information is stored as "periods" within the event:
// $event->getPeriods();
// Gets the last event period:
// $event->getLastPeriod();
事件可通过getDuration、getMemory等取得汇总指标;getPeriods返回所有分段,getLastPeriod取得最后一段。这样既能观察整体耗时,也能发现个别记录的异常开销。
用section分组时间线
section把分析时间线切成若干组。先openSection,再记录事件,通过stopSection给这组命名;之后可读取事件,也可以用相同名称再次打开,继续添加。
建立、读取并重新打开parsing分区
$this->stopwatch->openSection();
$this->stopwatch->start('validating-file', 'validation');
$this->stopwatch->stopSection('parsing');
$events = $this->stopwatch->getSectionEvents('parsing');
// later you can reopen a section passing its name to the openSection() method
$this->stopwatch->openSection('parsing');
$this->stopwatch->start('processing-file');
$this->stopwatch->stopSection('parsing');
没有进入任何命名section的事件会被放入特殊的__root__分区。无需提前知道事件名称,也可以通过Stopwatch::ROOT枚举它们:
读取根分区中的事件
use Symfony\Component\Stopwatch\Stopwatch;
foreach($this->stopwatch->getSectionEvents(Stopwatch::ROOT) as $event) {
echo (string) $event;
}
三、继续优化的方向
原文最后把Varnish缓存指南作为延伸阅读。应用和PHP配置处理不了的响应缓存问题,可以继续沿HTTP缓存方向分析。任何优化都应保留可比的工作负载与测量条件,确认收益之后再纳入正式部署。
来源、署名与许可
原文页面明确声明:正文及代码示例采用 Creative Commons Attribution-ShareAlike 3.0(CC BY-SA 3.0)。本文为该文中文译写,保留 Symfony 文档贡献者署名与来源,中文改编部分同样按 CC BY-SA 3.0 提供。原创技术图也按 CC BY-SA 3.0 提供。
- 官方原文
- 固定8.1版本原文
- 原文CC BY-SA3.0许可
- PHP OPcache
- Composer自动加载优化
- deepclone扩展
- PHP PIE
- cachetool
- Blackfire
- Stopwatch组件
- Varnish与Symfony
- Symfony 8.1安装要求
本文为原文的中文全文译写;代码保留原有技术结构,英文标识符、代码注释和演示文本保留,编者补充已明确标注。原创流程图用于解释正文,不代表实测结果。












暂无评论内容