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 控制设置。
- 设置值为 0 或 OFF 将禁用缓存
- 设置值为 1 或 ON 将缓存除了以 SELECT SQL_NO_CACHE 开头之外的所有选择
- 设置值为 2 或 DEMAND 只会缓存以 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 =1
log-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.log
Reading mysql slow query log from /var/log/mysql/mysql-slow.log
Count: 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 工程师编译整理)
PHP 性能分析第三篇: 性能调优实战的更多相关文章
- 第三篇、调优之路 Apache调优
1. 简介 在第一篇中整合了apache + tomcat ,利用了apache解析静态文件为tomcat解压.但是在测试机上发现两者性能不足,不能充分利用服务器的性能,该篇中将对apache进行性 ...
- 【转】一文掌握 Linux 性能分析之网络篇
[转]一文掌握 Linux 性能分析之网络篇 比较宽泛地讲,网络方向的性能分析既包括主机测的网络配置查看.监控,又包括网络链路上的包转发时延.吞吐量.带宽等指标分析.包括但不限于以下分析工具: pin ...
- 一文掌握 Linux 性能分析之网络篇
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 这是 Linu ...
- 【转】一文掌握 Linux 性能分析之内存篇
[转]一文掌握 Linux 性能分析之内存篇 前面我们已经学习了 CPU 篇,这篇来看下内存篇. 01 内存信息 同样在分析内存之前,我们得知到怎么查看系统内存信息,有以下几种方法. 1.1 /pro ...
- 【转】一文掌握 Linux 性能分析之 CPU 篇
[转]一文掌握 Linux 性能分析之 CPU 篇 平常工作会涉及到一些 Linux 性能分析的问题,因此决定总结一下常用的一些性能分析手段,仅供参考. 说到性能分析,基本上就是 CPU.内存.磁盘 ...
- 【转】一文掌握 Linux 性能分析之网络篇(续)
[转]一文掌握 Linux 性能分析之网络篇(续) 在上篇网络篇中,我们已经介绍了几个 Linux 网络方向的性能分析工具,本文再补充几个.总结下来,余下的工具包括但不限于以下几个: sar:统计信息 ...
- 一文掌握 Linux 性能分析之网络篇(续)
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 这是 Linu ...
- 高性能 Java 计算服务的性能调优实战
作者:vivo 互联网服务器团队- Chen Dongxing.Li Haoxuan.Chen Jinxia 随着业务的日渐复杂,性能优化俨然成为了每一位技术人的必修课.性能优化从何着手?如何从问题表 ...
- spring-petclinic性能调优实战(转)
1.spring-petclinic介绍 spring-petclinic是spring官方做的一个宠物商店,结合了spring和其他一些框架的最佳实践. 架构如下: 1)前端 Thymeleaf做H ...
随机推荐
- C# 日期减法
public class DateExample { public static void Main() { DateTime dt1 = new DateTime(2012, 7, 16); Dat ...
- 剑指offer——替换字符串
总结:先计算出总共有多少空格,count++:然后从后往前遍历,每遇到一个空格,count--: 替换空格 参与人数:2119时间限制:1秒空间限制:32768K 通过比例:20.23% ...
- win2003域控制器密码遗忘如何修改
在公司遇到这么个事儿,员工搭建QC服务器,设置了域账户登陆系统.但忘记了登录密码,使用PE直接修改sam文件不好用. 1.使用PE进系统修改组登陆方式的账号administrator密码 需符合复 ...
- L007-oldboy-mysql-dba-lesson07
L007-oldboy-mysql-dba-lesson07 [root@web01 ~]# mysqldump -uroot -ptestpassword -A >/root/mysql_ba ...
- 巧用Systemtap注入延迟模拟IO设备抖动
原创文章,转载请注明: 转载自系统技术非业余研究 本文链接地址: 巧用Systemtap注入延迟模拟IO设备抖动 当我们的IO密集型的应用怀疑设备的IO抖动,比如说一段时间的wait时间过长导致性能或 ...
- C#代码分层的好处
1.对于复杂的系统,分层让代码结构清晰,便于开发人员对系统进行整体的理解.把握.如果代码没有分层,把逻辑都写在一个方法里面的代码就好比是一本没有目录的文档,要找出其中某一节都要对全文遍览一次. 2.基 ...
- jQuery Easyui DataGrid应用
冻结列 $('#tbList').datagrid({ pagination: true, frozenColumns: [[ { field: 'BId',checkbox:'true',width ...
- PNG兼容IE6解决方法
虽然说现在早就不用ie6浏览器了,可以还是有一小部分还在使用 ,刚好公司也有要求~~~ <p> E6不兼容png图片,确实让网页的图片质量大大下降,为了兼容万恶的IE6,总结了下面几种方法 ...
- 破解网络投票IP限制、验证码限制、COokie限制、Seesion限制的方法!(转)
顾名思义,网络投票就是在网络上进行的投票活动,但和其他类型的投票不同的是:网络投票是建立在网络投票系统上的,而结果完全由程序输出,无需人工参与.这既是网络投票系统的优点也是其缺点,没有了人工的参与,其 ...
- Spark小课堂Week6 启动日志详解
Spark小课堂Week6 启动日志详解 作为分布式系统,Spark程序是非常难以使用传统方法来进行调试的,所以我们主要的武器是日志,今天会对启动日志进行一下详解. 日志详解 今天主要遍历下Strea ...