搜索
首页后端开发php教程PHP 性能分析第三篇: 性能调优实战

注意:本文是我们的 PHP 性能分析系列的第三篇,点此阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,或  PHP 性能分析第二篇: 深入研究 XHGui 。

在本系列的 第一篇 中,我们介绍了 XHProf 。而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中!

性能调优

不用运行的代码才是绝好的代码。其他只是好的代码。所以,性能调优时,最好的选择是首先确保运行尽可能少的代码。

OpCode 缓存

首先,最快且最简单的选择是启用 OpCode 缓存。OpCode 缓存的更多信息可以在 这里 找到。

在上图,我们看到启用 Zend OpCache 后发生的情况。最后一行是我们的基准,也即没有启用缓存的情况。

在中间行,我们看到较小的性能提升,以及内存使用量的大幅减少。小的性能提升(很可能)来自 Zend OpCache 优化,而非 OpCode 缓存。

第一行是优化和 OpCode 缓存后结果,我们看到很大的性能提升。

现在,我们看看 APC 之前和之后的变化。如上图所示,跟 Zend OpCache 相比,随着缓存的建立,我们看到初始(中间行)请求的性能下降,在消耗时长与内存使用量方面的表现都明显下降。

接着,随之 opcode 缓存的建立,我们看到类似的性能提升。

内容缓存

第二件我们能做的事是缓存内容??这对 WordPress 而言小菜一碟。它提供了许多安装简便的插件来实现内容缓存,包括 WP Super Cache。WP Super Cache 会创建网站的静态版本。该版本会在出现诸如评论事件时依照网站设置自动过期。(例如,在非常高负载情况下,您可能会想禁止任何原因造成的缓存过期)。

内容缓存只能在几乎没有写操作时有效运行,写操作会使缓存失效,而读操作不会。

你也应该缓存应用从第三方 API 处收到的内容,从而减少由于 API 可用性导致的延迟与依赖。
 
WordPress 有两个缓存插件,可以大大提高网站的性能: W3 Total Cache 和 WP Super Cache。

这两个插件都会创建网站的静态 HTML 副本,而不是每次收到请求时再生成页面,从而压缩响应时间。

如果你正在开发自己的应用程序,大多数框架都有缓存模块: 

  • Zend Framework 2:Zend\Cache

  • Symfony 2:Multiple options

  • Laravel 4:Laravel Cache

  • ThinkPHP 3.2.3:ThinkPHP  Cache

查询缓存

另一个缓存选项是查询缓存。针对 MySQL,有一个通用的查询缓存帮助极大。对于其他数据库,将查询结果集缓存在 Memcached 或者 cassandra 这样的内存缓存,也非常有效。

跟内容缓存一样,查询缓存在包含大量读取操作的场景是最有效的。由于少量的数据改动就会使大块的缓存区无效,尤其不能在这种情况下依赖 MySQL 查询缓存来提高性能。

查询缓存或许在生成内容缓存时对性能有提升。

如下图所示,当我们开启查询缓存后,实际运行时间减少了 40% ,尽管内存使用量没有明显改变。

现有三种类型的缓存选项,由 query_cache_type 控制设置。

  • 设置值为 0OFF 将禁用缓存

  • 设置值为 1ON 将缓存除了以 SELECT SQL_NO_CACHE 开头之外的所有选择

  • 设置值为 2DEMAND 只会缓存以 SELECT SQL_CACHE 开头的选择

此外,你应该将 query_cache_size 设置为非零值。将它设置为零将禁用缓存,不管 query_cache_type 是否设置。

想得到设置缓存的帮助,与许多其他性能相关的设置,请查看 mysql-tuning-primer 脚本。

MySQL 查询缓存的主要问题是,它是全局的。对缓存结果集构成的表格的任何更改都将导致缓存失效。在写入操作频繁的应用程序中,这将使缓存几乎无效。

然而,你还有许多其他选择,可以根据你的需求和数据集建立更多的智能缓存,例如 Memcached , riak , cassandra 或 redis

查询优化

如前所述,数据库查询常常是程序执行缓慢的原因,查询优化往往能比代码优化带来更多切身的好处。

查询优化有助于生成内容缓存时提高性能,而且,在无法缓存这种最坏的情况下也有益处。

除了分析, MySQL 还有一个帮助识别慢查询的选择??慢查询日志。慢查询日志会记录所有耗时超过指定时间的查询,以及不使用索引的查询(后者为可选项)。

您可以在 my.cnf 中使用以下配置启用日志。

[mysqld]log_slow_queries =/var/log/mysql/mysql-slow.log long_query_time =1log-queries-not-using-indexes

任何查询如果慢于 long_query_time (以秒为单位),该查询就会记录到日志文件 log_slow_queries 中。默认值是10秒,最低1秒。

此外, log-queries-not-using-indexes 选项可以将任何不使用索引的查询捕获到日志中。

之后我们可以用与 MySQL 捆绑在一起的 mysqldumpslow 命令检查日志。

在 WordPress 安装时使用这些选项 ,主页加载完成并运行后得到如下数据: 

$ mysqldumpslow -g "wp_" /var/log/mysql/mysql-slow.logReading mysql slow query log from /var/log/mysql/mysql-slow.logCount: 1  Time=0.00s (0s) Lock=0.00s (0s) Rows=358.0(358), user[user]@[host] SELECT option\_name, option\_value FROM wp_options WHERE autoload ='S'Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=41.0(41), user[user]@[host] SELECT user\_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (N)

首先,注意所有字符串值都以 S 表示,数字则以 N 表示。你可以添加 -a 标志来显示这些值。 

接下来,请注意,这两个查询均耗时 0.00 s,这意味着他们的耗时在 1 秒的阈值以下,且没有使用索引。

在 MySQL 控制台 使用 EXPLAIN,可以检查性能下降的原因:

    mysql> EXPLAIN SELECT option_name, option_value FROM wp_options WHERE autoload = 'S'\G    *************************** 1. row ***************************               id: 1      select_type: SIMPLE            table: wp_options             type: ALL    possible_keys: NULL              key: NULL          key_len: NULL              ref: NULL             rows: 433            Extra: Using where

此处,我们看到 possible_keys 是 NULL,从而确认未使用索引。

EXPLAIN 是对优化 MySQL 查询非常强大的工具,更多信息可以在 这里 找到。

PostgreSQL 同样也包括一个 EXPLAIN (该 EXPLAIN 与 MySQL 的差别很大),而 MongoDB 有$explain 元 操作符。

代码优化

通常只有当你不再受到 PHP 本身限制(通过使用 OpCode 缓存),缓存了尽可能多的内容,优化了查询之后,才可以开始调整代码。

代码和查询优化带来足够的性能提升才能创建其他缓存;代码在最糟糕的环境(没有缓存)下性能越高,应用就越稳定,重建缓存的速度也就越快。

让我们看看如何(潜在地)优化我们的 WordPress 安装。

首先,让我们看看最慢的函数: 

令我惊讶的是,列表中的第一项 不是 MySQL (事实上 mysql_query() 是第四),而是 apply_filter() 函数。

WordPress 代码库的特点是,通过基于事件的过滤系统执行多种数据转换,执行次序按照数据经内核、插件添加或回调的顺序。

apply_filter() 函数是这些回调应用的地方。

首先,你可能会注意到,函数被调用 4194 次。如果我们点击查看更多细节,就可以按照“调用次数”降序排列“父函数”,从而发现 translate() 调用了__apply_filter()__ 函数 778 次。

这很有趣,因为实际上我不使用任何翻译。我(并怀疑大多数用户)在使用 WordPress 软件时都设置为本土语言:英语。

因此,让我们点击查看细节,进一步查看该 translate() 函数在做什么。

在这里,我们看到两间有趣的事。首先,在父函数中,有一个被调用了773次:__()。

查看该函数的源代码后,我们发现它是 translate() 的包装器。

    <?php    /**     * Retrieves the translation of $text. If there is no translation, or     * the domain isn't loaded, the original text is returned.     *     * @see translate() An alias of translate()     * @since 2.1.0     *     * @param string $text Text to translate     * @param string $domain Optional. Domain to retrieve the translated text     * @return string Translated text     */    function __( $text, $domain = 'default' ) {        return translate( $text, $domain );    }    ?>

根据经验法则,函数调用代价昂贵,应该尽量避免。现在我们总是调用 __() 而不是 translate() ,我们应该把别名改为 translate() 来保持向后兼容性,而 __() 则不再调用非必要的函数。

然而,实际上,这种改变不会带来多大的差异,只是微观的优化罢了??但它的确提高了代码可读性,简化了调用图。

继续前进,让我们看看子函数:

现在,深入该函数,我们看到有 3 个 函数或方法被调用,每个 778 次:

  • get_translations_for_domain()

  • NOOP_Translations::translate()

  • apply_filters()

按照包容性实际运行时间降序排列,我们看到 apply_filter() 是目前为止耗时最长的调用。

查看代码:

    <?php    function translate( $text, $domain = 'default' ) {        $translations = get_translations_for_domain( $domain );        return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );    }    ?>

这段代码的作用是检索一个翻译对象,然后将 $translations->translate() 的结果传给 apply_filter() 。我们发现 $translations 是 NOOP_Translations 类的一个实例。

仅根据名称(__NOOP__),再经代码中的注释证实,我们发现翻译器实际上没有任何动作!

    <?php    /**     * Provides the same interface as Translations, but doesn't do anything     */    class NOOP_Translations {    ?>

因此,也许我们完全可以避免这种代码!

通过在代码上进行小规模调试,我们看到当前使用的是默认的域,我们可以修改代码以忽略翻译器:

    <?php    function translate( $text, $domain = 'default' ) {        if ($domain == 'default') {            return apply_filters( 'gettext', $text, $text, $domain );        }        $translations = get_translations_for_domain( $domain );        return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );    }    ?>

接下来,我们再次分析,确保要运行至少两次??确保所有缓存都建立,才是公平的对比!

这次运行的确更快!但是,快多少?为什么?

使用 XHGui 的比较运行这一特性就能找到答案。回到我们最初的运行,点击右上角的 “比较此处运行” 按钮,并从列表中选择新的运行。

我们发现,函数调用的次数减少了3% ,包容性实际运行时间减少 9% ,包容性CPU时间减少12%! 

之后,可以按调用次数降序排列细节页,这证实(如同我们的预期) get_translations_for_domain()NOOP_Translations::translate() 函数的调用次数减少。同样,可以确认没有预料之外的变化发生。

30 分钟的工作带来9 - 12% 的性能提升,这非常可喜。这就意味着真实世界的性能收益,即便是在应用了 opcache 之后。

现在我们可以对其函数重复这个过程,直到找不到更多优化点。

注意:此更改已提交到 WordPress.org 并已获更新。你可以在 WordPress Bug Tracker 跟踪讨论,查看实践过程。此更新计划包含在 WordPress 4.1 版本中。

其他工具

除了出色的 XHProf/XHGui,还有一些很好的工具。

New Relic & OneAPM

New Relic 与 OneAPM  均提供前后端性能分析;洞察后台堆栈讯息,包括 SQL 查询与代码分析,前端 DOM 与 CSS 呈现,以及 Javascript 语句。__OneAPM__ 更多功能请移步 (OneAPM 在线DEMO) 

uprofiler

uprofiler 是目前还未发布的 Facebook XHProf 分支,该分支计划删除 Facebook 所需的 CLA。目前,两者具备相同的特性,只有一些部分重命名了。

XHProf.io

XHProf.io 是 XHProf 的另一种用户界面。XHProf.io 在配置文件存储使用 MySQL ,用户友好性方面不及 XHGui。

Xdebug

在 XHProf 出现之前,Xdebug 早已存在??Xdebug 是一种主动的性能分析器,这意味着它不应该用于生产环境,但可以深入了解代码。

然而,它必须与另一个工具配合使用以读取分析器的输出 , 比如 KCachegrind)。但是 KCachegrind 很难安装在非 linux 机器上。另一个选择是 Webgrind。

Webgrind 无法提供 KCachegrind 的那些特性,但它是一个 PHP Web 应用程序,在任何环境都易于安装。

若搭配 KCachegrind ,你可以轻易探索并发现性能问题。(事实上,这是我最喜欢的剖析工具!)

结语

分析和性能调优是非常复杂的工程。有了对的工具,并理解如何善用这些工具,我们可以很大程度地提高代码质量??即使是对我们不熟悉的代码库。

花时间去探索和学习这些工具是绝对值得的。

注意:本文是我们的 PHP 性能分析系列的第三篇,阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,和  PHP 性能分析第二篇: 深入研究 XHGui 。(本文系应用性能管理领军企业 OneAPM 工程师编译整理

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
您如何防止与会议有关的跨站点脚本(XSS)攻击?您如何防止与会议有关的跨站点脚本(XSS)攻击?Apr 23, 2025 am 12:16 AM

要保护应用免受与会话相关的XSS攻击,需采取以下措施:1.设置HttpOnly和Secure标志保护会话cookie。2.对所有用户输入进行输出编码。3.实施内容安全策略(CSP)限制脚本来源。通过这些策略,可以有效防护会话相关的XSS攻击,确保用户数据安全。

您如何优化PHP会话性能?您如何优化PHP会话性能?Apr 23, 2025 am 12:13 AM

优化PHP会话性能的方法包括:1.延迟会话启动,2.使用数据库存储会话,3.压缩会话数据,4.管理会话生命周期,5.实现会话共享。这些策略能显着提升应用在高并发环境下的效率。

什么是session.gc_maxlifetime配置设置?什么是session.gc_maxlifetime配置设置?Apr 23, 2025 am 12:10 AM

thesession.gc_maxlifetimesettinginphpdeterminesthelifespanofsessiondata,setInSeconds.1)它'sconfiguredinphp.iniorviaini_set().2)abalanceIsiseededeedeedeedeedeedeedto to to avoidperformance andununununununexpectedLogOgouts.3)

您如何在PHP中配置会话名?您如何在PHP中配置会话名?Apr 23, 2025 am 12:08 AM

在PHP中,可以使用session_name()函数配置会话名称。具体步骤如下:1.使用session_name()函数设置会话名称,例如session_name("my_session")。2.在设置会话名称后,调用session_start()启动会话。配置会话名称可以避免多应用间的会话数据冲突,并增强安全性,但需注意会话名称的唯一性、安全性、长度和设置时机。

您应该多久再生一次会话ID?您应该多久再生一次会话ID?Apr 23, 2025 am 12:03 AM

会话ID应在登录时、敏感操作前和每30分钟定期重新生成。1.登录时重新生成会话ID可防会话固定攻击。2.敏感操作前重新生成提高安全性。3.定期重新生成降低长期利用风险,但需权衡用户体验。

如何在PHP中设置会话cookie参数?如何在PHP中设置会话cookie参数?Apr 22, 2025 pm 05:33 PM

在PHP中设置会话cookie参数可以通过session_set_cookie_params()函数实现。1)使用该函数设置参数,如过期时间、路径、域名、安全标志等;2)调用session_start()使参数生效;3)根据需求动态调整参数,如用户登录状态;4)注意设置secure和httponly标志以提升安全性。

在PHP中使用会议的主要目的是什么?在PHP中使用会议的主要目的是什么?Apr 22, 2025 pm 05:25 PM

在PHP中使用会话的主要目的是维护用户在不同页面之间的状态。1)会话通过session_start()函数启动,创建唯一会话ID并存储在用户cookie中。2)会话数据保存在服务器上,允许在不同请求间传递数据,如登录状态和购物车内容。

您如何在子域中分享会议?您如何在子域中分享会议?Apr 22, 2025 pm 05:21 PM

如何在子域名间共享会话?通过设置通用域名的会话cookie实现。1.在服务器端设置会话cookie的域为.example.com。2.选择合适的会话存储方式,如内存、数据库或分布式缓存。3.通过cookie传递会话ID,服务器根据ID检索和更新会话数据。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!