使用 Blackfire 进行 PHP 级性能优化
本文是关于构建示例应用程序(多图像库博客)以进行性能基准测试和优化的系列文章的一部分。 (在此处查看回购协议。)
在过去的几个月里,我们介绍了 Blackfire 及其用于检测应用程序性能瓶颈的方法。 在这篇文章中,我们将把它应用到我们刚开始的项目中,尝试找到我们可以采摘的低点和容易实现的成果,以提高我们应用程序的性能。
如果您正在使用 Homestead Improved(您应该使用),Blackfire 已经安装。
虽然在深入了解 Blackfire 之前先了解一下它很有用,但应用本文中的步骤不需要任何先验知识; 我们将从零开始。
设置
以下是评估 Blackfire 生成的图表时有用的术语。
参考配置文件:我们通常需要运行我们的第一个配置文件作为参考配置文件。 此配置文件将成为我们应用程序的性能基准。 我们可以将任何配置文件与参考进行比较,以衡量性能成就。
独占时间:一个函数/方法执行所花费的时间,不考虑其外部调用所花费的时间。
Inclusive Time:执行一个函数所花费的总时间,包括所有外部调用。
热路径:热路径是我们应用程序中在配置文件中最活跃的部分。 这些可能是消耗更多内存或占用更多 CPU 时间的部分。
第一步是在 Blackfire 上注册一个帐户。 帐户页面将包含需要放入的令牌和 ID Homestead.yaml
克隆项目后。 底部有一个用于所有这些值的占位符:
# blackfire:
# - id: foo
# token: bar
# client-id: foo
# client-token: bar
取消注释行并替换值后,我们需要安装 Chrome 伴侣。
Chrome 伴侣仅在需要手动触发分析时才有用——这将是您的大部分用例。 还有其他可用的集成,可以在此处找到完整列表。
使用 Blackfire 进行优化
我们将测试主页:登陆页面可以说是任何网站中最重要的部分,如果加载时间过长,我们肯定会失去访问者。 在 Google Analytics 开始记录反弹之前,它们就会消失! 我们可以测试用户添加图片的页面,但只读性能远比写入性能重要,所以我们将关注前者。
此版本的应用程序会加载所有画廊并按年龄对它们进行排序。
测试很简单。 我们打开要进行基准测试的页面,单击浏览器中的扩展程序按钮,然后选择“配置文件!”。
这是结果图:
实际上,我们在这里可以看到,从包含到排他的执行时间在 PDO 执行上是 100%。 具体来说,这意味着整个深粉色部分都花在了这个函数中,特别是这个函数没有等待任何其他函数。 这是正在等待的函数。 其他方法调用可能具有比 PDO 大得多的浅粉色条,但那些浅粉色部分是依赖函数的所有较小的浅粉色部分的总和,这意味着单独查看时,这些函数不是问题。 黑暗的需要先处理; 他们是当务之急。
此外,切换到 RAM 模式表明,虽然整个调用使用了几乎高达 40MB 的 RAM,但绝大多数都在 Twig 渲染中,这是有道理的:毕竟它显示了大量数据。
在图中,热路径有粗边框,通常表示存在瓶颈。 密集节点可以是热路径的一部分,也可以完全位于热路径之外。 密集节点是由于某种原因花费大量时间的节点,并且同样可以指示问题。
通过查看最有问题的方法并点击相关节点,我们可以确定 PDOExecute 是最有问题的瓶颈,而 unserialize
相对于其他方法使用最多的 RAM。 如果我们进行一些侦探工作并遵循相互调用方法的流程,我们会注意到这两个问题都是由于我们在主页上加载了整个画廊集而引起的。 PDOExecute 需要永远的内存和墙时间来找到它们并对它们进行排序,而 Doctrine 需要很长时间和无尽的 CPU 周期才能将它们变成可渲染的实体 unserialize
循环遍历它们 twig
模板。 解决方案看起来很简单——在主页上添加分页!
通过添加一个 PER_PAGE
常进 HomeController
并将其设置为类似 12
,然后在获取过程中使用该分页常量,我们阻止对最新的 12 个画廊的第一次调用:
$galleries = $this->em->getRepository(Gallery::class)->findBy([], ['createdAt' => 'DESC'], self::PER_PAGE);
当用户滚动到页面末尾时,我们将触发延迟加载,因此我们需要在主页视图中添加一些 JS:
{% block javascripts %}
{{ parent() }}
<script>
$(function () {
var nextPage = 2;
var $galleriesContainer = $('.home__galleries-container');
var $lazyLoadCta = $('.home__lazy-load-cta');
function onScroll() {
var y = $(window).scrollTop() + $(window).outerHeight();
if (y >= $('body').innerHeight() - 100) {
$(window).off('scroll.lazy-load');
$lazyLoadCta.click();
}
}
$lazyLoadCta.on('click', function () {
var url = "{{ url('home.lazy-load') }}";
$.ajax({
url: url,
data: {page: nextPage},
success: function (data) {
if (data.success === true) {
$galleriesContainer.append(data.data);
nextPage++;
$(window).on('scroll.lazy-load', onScroll);
}
}
});
});
$(window).on('scroll.lazy-load', onScroll);
});
</script>
{% endblock %}
由于注释被用于路由,因此很容易将一个新方法添加到 HomeController
在触发时延迟加载我们的画廊:
/**
* @Route("/galleries-lazy-load", name="home.lazy-load")
*/
public function homeGalleriesLazyLoadAction(Request $request)
{
$page = $request->get('page', null);
if (empty($page)) {
return new JsonResponse([
'success' => false,
'msg' => 'Page param is required',
]);
}
$offset = ($page - 1) * self::PER_PAGE;
$galleries = $this->em->getRepository(Gallery::class)->findBy([], ['createdAt' => 'DESC'], 12, $offset);
$view = $this->twig->render('partials/home-galleries-lazy-load.html.twig', [
'galleries' => $galleries,
]);
return new JsonResponse([
'success' => true,
'data' => $view,
]);
}
比较
现在让我们通过重新运行探查器来比较我们升级后的应用程序与以前的版本。
果然,我们的网站使用的内存减少了 10 倍,加载速度也快得多——也许不是 CPU 时间,如图中的秒表所示,而是印象中的。 重新加载现在几乎是即时的。
该图现在向我们展示了 DebugClass 是资源最密集的方法调用。
发生这种情况是因为我们处于开发模式,并且这个类加载器通常比生产类加载器慢得多,因为它没有大量缓存类。 这是必要的,以便可以立即测试代码中所做的更改,而无需清除 APC 缓存或正在使用的任何其他缓存。
如果我们切换到 prod
模式只是为了这个测试的目的,我们会看到一个明显的区别:
结论
我们应用程序的速度现在令人难以置信——加载页面只需 58 毫秒,而且看不到类加载器。 请注意,这一切都发生在具有数千个虚拟数据条目的虚拟机中。 此时我们可以对我们应用程序的生产状态感到非常乐观:主页上几乎没有优化; 其他一切都可以归类为微优化。
定期重新运行这些性能测试对于任何应用程序的开发周期都很重要,并将它们集成到应用程序的测试管道中,如 CD/CI 流程,可能非常有用且富有成效。 稍后我们会查看该选项,但重要的是要注意 Blackfire 的高级订阅实际上提供了内置的这个东西。检查一下!
现在,重要的是我们安装了 Blackfire 并使其可用,它可以很好地帮助我们找到瓶颈并在我们向组合中添加更多功能时识别新瓶颈。 欢迎来到持续性能测试的世界!