李落 2010年10月20日 星期三 19:21 | 1850次浏览 | 0条评论
本来我一直不知道怎么来更好地优化网页的性能,然后最近做 python 和 php 同类网页渲染速度比较时,意外地发现一个很简单很白痴但是我一直没发现的好方法(不得不 BS 我自己):直接像某些 php 应用比如 Discuz 论坛那样,在生成的网页中打印出 “ 本页面生成时间多少多少秒 ” ,然后在不停地访问网页测试时,很直观地就能发现什么操作会导致瓶颈,怎样来解决瓶颈了。
于是我发现 SimpleCD 在生成首页时,意外地竟然需要 0.2 秒左右,真真不能忍:对比 Discuz 论坛首页平均生成才 0.02 秒,而 Discuz 论坛的首页页面无疑比 SimpleCD 的主页要复杂不少;这让我情何以堪啊,因为这必然不是 Python 语言导致的差距,只能说是我完全没做优化而 Discuz 程序优化得很好的后果。
优化分析
其实不用分析也能知道肯定是数据库在拖累, SimpleCD 在生成首页时需要在 sqlite 的三个数据库中进行 42 多次查询,是历史原因导致的极其低效的一个设计;但是这 40 多次查询中,其实大部分是非常快的查询,仔细分析一下就有两个是性能大户,其他都不慢。
第一个大户就是:获取数据个数
SELECT count(*) FROM verycd;
这个操作每次都要花不少时间,这是因为每次数据库都要锁住然后遍历一遍主键统计个数的缘故,数据量越大耗时就越大,耗时为 O(N) , N 为数据库大小;实际上解决这个问题非常容易,只要随便在哪存一个当前数据的个数,只有在增删数据的时候改动就行了,这样时间就是 O(1) 的了。
第二个大户就是:获取最新更新的 20 个数据列表
SELECT verycdid,title,brief,updtime FROM verycd
ORDER BY updtime DESC LIMIT 20;
因为在 updtime 上面做了索引,所以其实真正查询时间也就是搜索索引的时间而已。然则为什么这个操作会慢呢?因为我的数据是按照 publish time 插入的,按 update time 进行显示的话就肯定需要在至少 20 个不同的地方做 I/O ,这么一来就慢了。解决的方法就是让它在一个地方做 I/O 。也就是,除非数据库加入新数据 / 改变原有数据,否则把这条语句的返回结果缓存起来。这么一来又快了 20 倍:)
接下来的是 20 条小 case :取得发布人和点击数信息
SELECT owner FROM LOCK WHERE id=XXXX;
SELECT hits FROM stat WHERE id=XXXX;
这里为什么没用 sql 的 join 语句来省点事呢?因为架构原因这些数据放在不同的数据库里, stat 是点击率一类的数据库,因为需要频繁的插入所以用 mysql 存储;而 lock 和 verycd 是需要大量 select 操作的数据库,因为 mysql 悲剧的索引使用情况和分页效率而存放在了 sqlite3 数据库,所以无法 join -.-
总之这也不是问题,跟刚才的解决方法一样,统统缓存
所以纵观我这个例子,优化网页性能可以一言以蔽之,缓存数据库查询,即可。我相信大部分网页应用都是这样:)
Memcached 终于出场
终于轮到 memcached 了,既然打算缓存,用文件做缓存的话还是有磁盘 I/O ,不如直接缓存到内存里面,内存 I/O 可就快多了。于是 memcached 顾名思义就是这么个东东。
memcached 是很强大的工具,因为它可以支持分布式的共享内存缓存,大站都用它,对小站点来说,只要出得起内存,这也是好东西;首页所需要的内存缓冲区大小估计不会超过 10K ,更何况我现在也是内存土豪了,还在乎这个?
安装: ubuntu 下还是方便啊
apt-get install memcached
apt-get install python-memcached
配置运行:因为是单机没啥好配的,改改内存和端口就行了
vi /etc/memcached.conf
/etc/init.d/memcached restart
在 python 的网页应用中使用之
import memcache
mc = memcache.Client(['127.0.0.1:11211'], debug=0)
memcache 其实就是一个 map 结构,最常使用的就是两个函数了:
§ 第一个就是 set(key,value,timeout) ,这个很简单就是把 key 映射到 value , timeout 指的是什么时候这个映射失效
§ 第二个就是 get(key) 函数,返回 key 所指向的 value
于是对一个正常的 sql 查询可以这么干
sql = 'select count(*) from verycd'
c = sqlite3.connect('verycd.db').cursor()
# 原来的处理方式
c.execute(sql)
count = c.fetchone()[0]
# 现在的处理方式
from hashlib import md5
key=md5(sql)
count = mc.get(key)
if not count:
c.execute(sql)
count = c.fetchone()[0]
mc.set(key,count,60*5) # 存 5 分钟
其中 md5 是为了让 key 分布更均匀,其他代码很直观我就不解释了。
优化结果和结论
优化过语句 1 和语句 2 后,首页的平均生成时间已经降低到 0.02 秒,和 discuz 一个量级了;再经过语句 3 的优化,最终结果是首页生成 时间降低到了 0.006 秒 左右,经过 memcached 寥寥几行代码的优化, 性能提高了 3300% 。终于可以挺直腰板来看 Discuz 了:)
说 memcached 是妖孽,并不是因为 memcached 应用了之后性能狂升 —— 这本是意料之中的事情,不这样反而才奇怪 —— 而是因为我基本上没花多少时间就实现了这么妖孽的效果,至少我花的时间并不比写这篇 blog 的时间多。那么方便的使用方法和那么显著的性能提升状况真是让人咋舌。
题外话
话说最近的一些优化测试比较中,我被 php 的性能吓了一跳,完全和 python 是一个级别的,以前因为浮点性能鄙视过 php 是我不对,太片面了。
不过想想这样才是合理啊呵呵,随便什么应用 IO 才是瓶颈,语言的性能真的太不重要了而且太容易改进了;特别是解释性脚本语言,解释器的版本进步就会对性能带来很大提升,单纯的比较性能没有多大意义。比如随着 java 解释器的发展 java 已经快到让人瞠目结舌了,比如 pypy 就比 CPython 在科学计算中快 5-10 倍(实测)。
如果真要比的话,易用性和资源占用才是值得比较的对象,因为易用意味着更高的开发效率,而更少内存意味着更多并发的可能。关于易用性我就不说了,从内存来说,通过简单的测试来看 php 比 python 多消耗 50%-100% 的内存,但这很大程度上是因为 php 某些时候过于傻瓜化的设定,并不能真的说明 php 这门语言的内存控制烂。不多说了,都是一些我觉得挺有趣的小发现吧。
虽说我这么说好像是拼命想证明 python 比 php 优越的样子,但是其实刚好相反,用过不少 php 下面很方便的工具后,我越来越觉得 php 不错了,至少用的人多网页方面的模块很多,有时候真的会省很多事。也许我应该找时间专门写一篇比较 php 和 python 在实际应用中的文 —— 而不是网上充斥的一些无意义的浮点数计算性能比较,就像我之前做过的那样 —— 如果有人想看的话:)
原地址链接: http://obmem.info/?p=717
Zeuux © 2024
京ICP备05028076号
暂时没有评论