网上很多文章介绍优化php程序,是通过安装Zend Optimizer之类的加速软件实现的,但这种加速是有限的。本文主要从程序代码着手介绍一些优化手段。
1、程序的抽象层越多,各抽象层分离得越严格,程序效率越低。
最原始的应用于网页的php程序模式莫过于脚本嵌入模式,即仅仅在一个网页中需要动态处理或显示数据的地方通过加入标识符嵌入php脚本。一般来说这是php程序员最早学习的模式,它只有一个抽象层,就是网页,故本文称其为单层模式。
随着网站规模逐渐增大,程序员可能会发现单层模式的程序很难维护,当想对程序修改或扩充功能时,会发现代码非常混乱,感觉无从下手。于是模板类诞生了,它使一个网页由两个文件组成:一个php程序文件,一个html模板文件。常用的模板类有PHPLib库带的Template模板类,Smarty模板类等。由于加入了额外的处理程序(模板类),程序效率下降了。你若不信可自己测试一下。其实一般情况下,不用函数(最原始的编程方法)比用函数(面向过程)的效率高,而用函数的效率又比对象封装(面向对象)高。所以就算在编译语言中,需要高效率的地方会用C写而不用C++,例如FreeBSD操作系统的内核;而需要极端高效的地方还要用汇编写。
为了使程序可以适应多种数据库系统,或者方便随时转换数据库系统,常常还会用一个类把跟数据库打交道的函数封装起来,这样当转换数据库系统时只要把封装类换掉就行了,主程序不需要修改。这里又用了一个类,效率又打折扣了。
上述模板类的使用,使程序分成两个抽象层:程序层和表现层。而数据库类的使用又把程序层分为数据接口层和数据处理层。
项目越庞大,需要分离的抽象层就越多,这样使得分工清晰,方便管理,但是以牺牲程序执行效率为代价。
对于抽象层造成的效率下降,优化的方法有二:减小抽象层、优化抽象层之间的接口。一般地,不应该为了提高效率而盲目减小抽象层,这样会使得代码混乱、难于管理。但是不应该为小项目建立过多的抽象层,除非你有将来把它做得很大的计划。关于如何恰当分割抽象层,本文不作更深入讨论。
对于上文说的两个分层例子,优化抽象层之间的接口分别是模板类和数据库操作类。抽象层接口在程序中需要被频繁调用,以在不同层之间交换信息,所以层接口是很值得优化的。对于数据库接口类,可能仅仅是封装一些数据库函数,优化余地恐怕不大。对于模板类,很多时候是有较大优化余地的。一般地,模板模型越通用,模板类功能越强大,效率就越低,例如PHPLib库带的Template类就有极大的优化余地。而Smarty模板类比PHPLib的Template更复杂,我没有用过,据称有缓存机制,不知是否可以弥补其性能损耗。下面就来看看PHPLib的Template类有多少东西可以优化掉。
(1) 读入模板文件时,file函数效率低,改用get_file_content函数。
(2) 匹配子模板时,正则表达式替换函数preg_replace效率低,改用str_pos函数进行定位和用str_replace函数进行替换操作。此优化手段后文会详细分析。
(3) 模板模型通用性很强,能适应各种情况,但在具体细节的处理上,通用的方法效率可能很低。可以对模板模型作适当修改。我的做法是建一个相对通用的模板类,然后再派生出一个只适用于特定程序的模板类。在通用模板类的模板模型上可以作些优化(相对于PHPLib的Template),例如在处理二维数据表的时候用PHPLib的Template处理就比较复杂,需要多次调用类方法(本质上是函数调用),所以重写的时候可以把处理二维数据表的功能封装到一个高效率的方法中,直接避免方法的多次调用。
(4) 调试功能在小项目上不需要用,跟调试相关的代码全部去掉。
我在按上面4点重写了模板类之后,一个复杂页面的执行时间缩小了一个数量级(除模板外没有作其它优化)。
现在就优化你的程序的抽象层之间的接口,特别是当这些接口是使用现成的函数或类的时候。因为这些函数或类在设计时会为了适应普遍情况而牺牲一些效率,而且它们的作者也可能没有考虑效率问题。像如此著名的PHPLib的模板类的效率也不见得就高。
2、细节代码优化
(1) 上文模板类优化已提到的,正则表达式匹配比一般字符串匹配慢得多,尽可能用字符串匹配而不用正则表达式匹配。有时候虽然用正则表达式匹配使程序代码更简洁,而一般字符串匹配使代码更冗繁,但很多时候字符串匹配仍比正则表达式高效。
(2) 字符串替换函数str_replace和preg_replace都是可以接受数组参数的。有时候需要对字符串进行批量替换,则用数组参数比循环调用替换函数来得高效。例如下面的代码:
for ($i = 0; $i < $n; $i++) {
$str = str_replace($search[$i], $replace[$i], $str);
}
应该换成:
$str = str_replace($search, $replace, $str);
注意:这里$search和$replace都是数组
str_replace和preg_replace的数组用法可参考PHP手册。
(3) 对用于赋值的条件语句,可改用?:算符
这里仅举三个细节代码优化的方法。实际上PHP程序还有很多细节代码优化方法,要掌握这些方法,需要多看PHP手册,多了解些函数。在解决一些细节问题时,用不同的函数作不同的搭配,就产生不同的方法,对不同的方法应进行实际效率测试,得出优化方法。
3、面向对象、面向过程、类、函数、宏
尽管面向对象方法在程序设计中有很多优点(这里就不罗列了),但一般地说,面向对象程序的执行效率往往不如面向过程好,一个显然的理由是面向对象的程序往往要频繁调用对象的方法从而使代码简洁明了,却降低了程序执行效率。
对于中小型项目,为程序效率着想,最好在思想上,面向对象与面向过程兼有,在代码上,类、函数、宏搭配使用。这里提到一个可能对读者陌生的概念――宏(macro)。在C语言中有宏,宏汇编中也有宏,但PHP中官方没有定义“宏”的概念。然而我们可以通过require函数和include函数实现宏的功能。require和include通常用来在程序代码中包含函数库或类库等文件,一般很少用来直接包含程序代码文件,因为包含程序代码有时会降低代码易读性。当有一段代码需要在很多页面中都执行时,通常被想到的是把它打包为函数或封装为类。但此法有缺点如下:
(1) 降低程序效率。因为增加了函数/类方法调用。
(2) 有时此段嵌入代码较复杂,实现功能并不单纯,封进函数并不符合函数功能单纯的原则
(3) 有时嵌入代码需要与外部大量交换数据,如果封进函数会使参数表庞大,且处理函数的返回值也变得复杂。
(4) 由于有以上(2)(3)两点缺点,导致当嵌入代码与外部代码交换的数据有所变化时,函数或类的接口的更改会变得麻烦。
当遇上以上情况时,我建议使用宏,即把嵌入代码直接写入被包含文件中,供各页面包含。但此时务必要清晰注释宏的调用方法,输入输出数据等,以弥补代码易读性的降低。
总之,面向对象、面向过程、类、函数、宏,这些都应根据具体实际情况而搭配使用,不应盲目死守某些原则。
4、SQL数据库
本来SQL数据库的优化不应归入PHP优化,但实际应用中PHP常与SQL数据库配合构建网站,常用的数据库有MySQL、PostgreSQL等。于是SQL查询的效率也直接影响PHP程序的效率,故本文也略为谈一谈。
(1) MySQL中,使用InnoDB或BDB表(支持事务)的效率比MyISAM表(不支持事务)的效率低,尤其InnoDB表效率比MyISAM低很多,而 BDB表的主要劣势在于占用磁盘空间比MyISAM表多很多,且不能查知每个表占用磁盘空间的大小。而对中小型应用,数据不一致性出现的概率是很微的,所以尽可使用MyISAM表提高效率。
(2) 恰当建立索引和存储冗余数据。此话题太大故本文不打算作详述。
(3) 有些SQL查询仅需知道数据库中是否存在符合条件的行,故只要查得一行,搜索即可结束。所以对此类查询,可在SQL句末加上LIMIT 1使数据库一旦搜到满足条件的行即不再搜索。
(4) 尽量少用JOIN,有些低效率的JOIN查询可通过存储冗余数据来避免。
(5) 有时候需要比较现在的时间与数据库某列的时间的差距,返回时间差距满足一定条件的行。在MySQL 4.1之前,是没有DATEDIFF函数的,但可以用DATE_ADD或DATE_SUB函数简洁实现,例如要返回xtime列距今超过10天的行,有两种写法:
法一:
SELECT somecolumn FROM sometable WHERE DATE_ADD(xtime, INTERVAL 10 DAY) < NOW()
法二:
SELECT somecolumn FROM sometable WHERE DATE_SUB(NOW(), INTERVAL 10 DAY) > xtime
两种写法执行结果等效但效率不同。如果xtime列建有索引,则法一的写法无法使用索引,而法二的写法可以用索引,故应采用第二种写法提高效率。
5、延迟输出与缓冲
有时候有些页面无论如何也无法优化到需要的速度,此时可以考虑使用延迟输出与缓冲的技术。
而缓冲也是一个大话题,故本文也不打算作详述,但作一简介。
常用的技术,按网站软件层次分,有网页服务软件的缓冲技术,例如Apache的缓冲技术;又有动态脚本的缓冲技术,例如用PHP编程实现的缓冲技术。
此外又可分为静态缓冲技术和动态缓冲技术。静态缓冲技术即把动态内容生成静态的html页面存于磁盘,客户端几乎完全跟静态页面打交道,整个网站犹如一个静态网站。而服务器后台在适当时候调用动态程序更新静态内容(重新生成静态内容)。动态缓冲技术则是客户端仍与动态页面打交道,而动态网页在接到客户请求时先检查是否有相应的缓冲网页,如有,直接把该静态页输出到客户端,如无或缓冲页已过时,则重新生成缓冲页面并输出到客户端。
新闻热点
疑难解答