Differences between revisions 6 and 7
Revision 6 as of 2004-10-06 00:12:55
Size: 6310
Editor: hoxide
Comment:
Revision 7 as of 2006-07-31 19:52:26
Size: 6452
Editor: eacheon
Comment:
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
'''
Python编程速度技巧'''
-- Zoom.Quiet [[[DateTime(2004-08-09T23:59:25Z)]]]
''' Python编程速度技巧''' -- Zoom.Quiet [[[DateTime(2004-08-09T23:59:25Z)]]]
Line 6: Line 4:
[[TableOfContents]]
[转载自oso,其上有“python编程简介”系列,这是第九篇,是篇译文]
有了前面的描述, 大家差不多知道什么是Python了. 最近因为在网
上看一两篇关于Python的运行速度问题, 不给大家介绍一下心里不
踏实, :-) 
[[TableOfContents]] [转载自oso,其上有“python编程简介”系列,这是第九篇,是篇译文]  有了前面的描述, 大家差不多知道什么是Python了. 最近因为在网  上看一两篇关于Python的运行速度问题, 不给大家介绍一下心里不  踏实, :)
Line 13: Line 7:
== 最常见 ==
 . 一个最常见的速度陷坑(至少是俺在没看到网上这篇介绍时陷进去
过好些次的) 是: 许多短字串并成长字串时, 大家通常会用:
Line 14: Line 11:
== 最常见 ==

 一个最常见的速度陷坑(至少是俺在没看到网上这篇介绍时陷进去
过好些次的) 是: 许多短字串并成长字串时, 大家通常会用:
Line 20: Line 13:
shortStrs = [ str0, str1, ..., strN] 
#N+1个字串所组成的数列 
longStr = '' 
for s in shortStrs: longStr += s 
shortStrs = [ str0, str1, ..., strN]
#N+1个字串所组成的数列
longStr = ''
for s in shortStrs: longStr += s
Line 25: Line 18:
因为Python里字串是不可变的, 所以每次 longStr += s 都是将原
来的 longStr 与 str 拷贝成一个新字串, 再赋给longStr. 随着
longStr的不断增长, 所要拷贝的内容越来越长. 最后导至str0被
拷贝N+1次, str1是N次, ... . 
因为Python里字串是不可变的, 所以每次 longStr += s 都是将原  来的 longStr 与 str 拷贝成一个新字串, 再赋给longStr. 随着  longStr的不断增长, 所要拷贝的内容越来越长. 最后导至str0被  拷贝N+1次, str1是N次, ... .
Line 30: Line 20:
那咋办呢 ? 咱们来看看Skip Montanaro先生的解说:
http://musi-cal.mojam.com/~skip/python/fastpython.html
及可参考一下Guido van Rossum本人的:
http://www.python.org/doc/essays/list2str.html 
那咋办呢 ? 咱们来看看Skip Montanaro先生的解说:  http://musi-cal.mojam.com/~skip/python/fastpython.html  及可参考一下Guido van Rossum本人的:  http://www.python.org/doc/essays/list2str.html
Line 36: Line 23:
 * 1)首先在大家应先学会怎么去找出速度瓶颈: Python自带有profile 
模块: 
 * 1)首先在大家应先学会怎么去找出速度瓶颈: Python自带有profile
模块:
Line 40: Line 28:
import profile 
profile.run ('想要检查的函数名()') 
import profile
profile.run ('想要检查的函数名()')
Line 43: Line 31:
就会打印出那个函数里调用了几次其它函数, 各用了多少时间,
总共用了多少时间等信息 --- Nice ? 详请参阅<<库参考>>中的
profile模块的论述. 
就会打印出那个函数里调用了几次其它函数, 各用了多少时间,  总共用了多少时间等信息 --- Nice ? 详请参阅<<库参考>>中的  profile模块的论述.
Line 47: Line 33:
当然脑袋笨一点或是聪明一点的, 也可以用time模块中的time()
来显示系统时间, 减去上次的time()就是与它的间隔秒数了. 
当然脑袋笨一点或是聪明一点的, 也可以用time模块中的time()  来显示系统时间, 减去上次的time()就是与它的间隔秒数了.
Line 51: Line 36:
 * 就头上的例子而言, 用 :   * 就头上的例子而言, 用 :
Line 54: Line 39:
longStr =''.join(shortStrs)  longStr =''.join(shortStrs)
Line 56: Line 41:
立马搞定, :-) 但如果shortStrs里面不都是字串, 而包含了些数
字呢 ? 直接用join就会出错. 不怕, 这样来: 
立马搞定, :) 但如果shortStrs里面不都是字串, 而包含了些数  字呢 ? 直接用join就会出错. 不怕, 这样来:
Line 61: Line 46:
longStr = ''.join(shortStrs)  longStr = ''.join(shortStrs)
Line 63: Line 48:
也即先将数列中所有内容都转化为字串, 再用join.  也即先将数列中所有内容都转化为字串, 再用join.
Line 65: Line 50:
对少数几个字串相并, 应避免用:
all = str0 + str1 + str2 + str3
而用:
all = '%s%s%s%s' % (str0, str1, str2, str3) 
对少数几个字串相并, 应避免用:  all = str0 + str1 + str2 + str3  而用:  all = '%s%s%s%s' % (str0, str1, str2, str3)
Line 71: Line 53:
 *  list.sort () 
你可以按特定的函数来: list.sort( 函数 ), 只要这个函数接受
两参数, 并按特定规则返回1, 0, -1就可以. --- 很方便吧? 但
会大大减慢运行速度. 下面的方法, 俺举例子来说明可能更容易
明白. 
 * list.sort ()
你可以按特定的函数来: list.sort( 函数 ), 只要这个函数接受  两参数, 并按特定规则返回1, 0, -1就可以. --- 很方便吧? 但  会大大减慢运行速度. 下面的方法, 俺举例子来说明可能更容易  明白.
Line 77: Line 56:
比方说你的数列是 l = ['az', 'by'], 你想以第二个字母来排序.
先取出你的关键词, 并与每个字串组成一个元组:
new = map (lambda s: (s[1], s), l ) 
比方说你的数列是 l = ['az', 'by'], 你想以第二个字母来排序.  先取出你的关键词, 并与每个字串组成一个元组:  new = map (lambda s: (s[1], s), l )
Line 81: Line 58:
于是new变成[('z', 'az'), ('y', 'by')], 再把new排一下序:
new.sort() 
于是new变成[('z', 'az'), ('y', 'by')], 再把new排一下序:  new.sort()
Line 84: Line 60:
则new就变成 [('y', 'by'), ('z', 'az')], 再返回每个元组中
的第二个字串:
sorted = map (lambda t: t[1], new) 
则new就变成 [('y', 'by'), ('z', 'az')], 再返回每个元组中  的第二个字串:  sorted = map (lambda t: t[1], new)
Line 88: Line 62:
于是sorted 就是: ['by', 'az']了. 这里的lambda与map用得很
好.
于是sorted 就是: ['by', 'az']了. 这里的lambda与map用得很 好.

Python2.4以后sort和sorted的使用可以参考这片 [http://wiki.python.org/moin/HowTo/Sorting#head-194f75a6010b83e8ecbe30d3541d0f96455d6323 Sort wiki].[http://wiki.python.org/moin/HowTo/Sorting#head-194f75a6010b83e8ecbe30d3541d0f96455d6323 ]
Line 93: Line 68:
比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用:
Line 94: Line 70:
比如for循环. 当循环体很简单时, 则循环的调用前头(overhead)
会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列
l=['a', 'b', ...]中的每个字串变成大写, 可能会用:
Line 99: Line 72:
import string 
newL = [] 
for s in l: newL.append( string.upper(s) ) 
import string
newL = []
for s in l: newL.append( string.upper(s) )
Line 103: Line 76:
用map就可以省去for循环的前头:  用map就可以省去for循环的前头:
Line 106: Line 80:
import string 
newL = map (string.upper, l) 
import string
newL = map (string.upper, l)
Line 109: Line 83:
Guido的文章讲得很详细.   Guido的文章讲得很详细.
Line 113: Line 86:
象上面, 若用 append = newL.append, 及换种import方法:  象上面, 若用 append = newL.append, 及换种import方法:
Line 116: Line 90:
import string
append = newL.append
for s in l: append (string.upper(s))
}}}
会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找.
Line 117: Line 96:
import string
append = newL.append
for s in l: append (string.upper(s))
俺自己就不比较时间了, Skip Montanaro的结果是:

{{{
基本循环: 3.47秒
去点用局域变量: 1.79秒
使用map: 0.54秒
Line 121: Line 103:
=== try的使用 ===
比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会:
Line 122: Line 106:
会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找.

俺自己就不比较时间了, Skip Montanaro的结果是:
{{{
基本循环: 3.47秒
去点用局域变量: 1.79秒
使用map: 0.54秒
}}}

=== try的使用 ===
比如你想计算一个字串数列:
l = ['I', 'You', 'Python', 'Perl', ...]
中每个词出现的次数, 你可能会:
Line 137: Line 108:
count = {} 
for s in l: 
    if not count.has_key(s): count[s] = 0 
    else: count[s] += 1 
count = {}
for s in l:
    if not count.has_key(s): count[s] = 0
    else: count[s] += 1
Line 142: Line 113:
由于每次都得在count中寻找是否已有同名关键词, 会很费时间.
而用try: 
由于每次都得在count中寻找是否已有同名关键词, 会很费时间.  而用try:
Line 146: Line 117:
count ={} 
for s in l: 
    try: count[s] += 1 
    except KeyError: count[s] = 0 
count ={}
for s in l:
    try: count[s] += 1
    except KeyError: count[s] = 0
Line 151: Line 122:
就好得多. 当然若经常出现例外时, 就不要用try了.   就好得多. 当然若经常出现例外时, 就不要用try了.
Line 155: Line 125:
这好理解. 就是避免在函数定义中来import一个模块, 应全在
全局块中来import  
这好理解. 就是避免在函数定义中来import一个模块, 应全在  全局块中来import
Line 160: Line 128:
由于Python中的函数调用前头(overhead)比较重, 所以处理大量
数据时, 应: 
由于Python中的函数调用前头(overhead)比较重, 所以处理大量  数据时, 应:
Line 164: Line 132:
def f(): 
for d in hugeData: ... 
f() 
def f():
for d in hugeData: ...
f()
Line 168: Line 136:
而不要:  而不要:
Line 171: Line 140:
def f(d): ... 
for d in hugeData: f(d) 
def f(d): ...
for d in hugeData: f(d)
Line 174: Line 143:
这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对
解释性语言就更重要了.  
这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对  解释性语言就更重要了.
Line 179: Line 146:
这是Python的本征功能:
周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理. 
这是Python的本征功能:  周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理.
Line 182: Line 148:
可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔.  可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔.
Line 184: Line 150:
缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次.  缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次.
Line 186: Line 152:
当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著.  当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著.
Line 189: Line 155:
---编/译完毕. 看来Python是易学难精了, 象围棋?  ---编/译完毕. 看来Python是易学难精了, 象围棋?
Line 191: Line 157:
Line 199: Line 164:
"由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: "
这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan
"由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: " 这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan

Python编程速度技巧 -- Zoom.Quiet [DateTime(2004-08-09T23:59:25Z)]

TableOfContents [转载自oso,其上有“python编程简介”系列,这是第九篇,是篇译文] 有了前面的描述, 大家差不多知道什么是Python了. 最近因为在网 上看一两篇关于Python的运行速度问题, 不给大家介绍一下心里不 踏实, :)

Python编程速度技巧

最常见

  • 一个最常见的速度陷坑(至少是俺在没看到网上这篇介绍时陷进去

过好些次的) 是: 许多短字串并成长字串时, 大家通常会用:

   1 shortStrs = [ str0, str1, ..., strN]
   2 #N+1个字串所组成的数列
   3 longStr = ''
   4 for s in shortStrs: longStr += s

因为Python里字串是不可变的, 所以每次 longStr += s 都是将原 来的 longStr 与 str 拷贝成一个新字串, 再赋给longStr. 随着 longStr的不断增长, 所要拷贝的内容越来越长. 最后导至str0被 拷贝N+1次, str1是N次, ... .

那咋办呢 ? 咱们来看看Skip Montanaro先生的解说: http://musi-cal.mojam.com/~skip/python/fastpython.html 及可参考一下Guido van Rossum本人的: http://www.python.org/doc/essays/list2str.html

找出速度瓶颈

  • 1)首先在大家应先学会怎么去找出速度瓶颈: Python自带有profile

模块:

   1 import profile
   2 profile.run ('想要检查的函数名()')

就会打印出那个函数里调用了几次其它函数, 各用了多少时间, 总共用了多少时间等信息 --- Nice ? 详请参阅<<库参考>>中的 profile模块的论述.

当然脑袋笨一点或是聪明一点的, 也可以用time模块中的time() 来显示系统时间, 减去上次的time()就是与它的间隔秒数了.

字串相并

  • 就头上的例子而言, 用 :

   1 longStr =''.join(shortStrs)

立马搞定, :) 但如果shortStrs里面不都是字串, 而包含了些数 字呢 ? 直接用join就会出错. 不怕, 这样来:

   1 shortStrs = [str(s) for s in shortStrs[i]]
   2 longStr = ''.join(shortStrs)

也即先将数列中所有内容都转化为字串, 再用join.

对少数几个字串相并, 应避免用: all = str0 + str1 + str2 + str3 而用: all = '%s%s%s%s' % (str0, str1, str2, str3)

数列排序

  • list.sort ()

你可以按特定的函数来: list.sort( 函数 ), 只要这个函数接受 两参数, 并按特定规则返回1, 0, -1就可以. --- 很方便吧? 但 会大大减慢运行速度. 下面的方法, 俺举例子来说明可能更容易 明白.

比方说你的数列是 l = ['az', 'by'], 你想以第二个字母来排序. 先取出你的关键词, 并与每个字串组成一个元组: new = map (lambda s: (s[1], s), l )

于是new变成[('z', 'az'), ('y', 'by')], 再把new排一下序: new.sort()

则new就变成 [('y', 'by'), ('z', 'az')], 再返回每个元组中 的第二个字串: sorted = map (lambda t: t[1], new)

于是sorted 就是: ['by', 'az']了. 这里的lambda与map用得很 好.

Python2.4以后sort和sorted的使用可以参考这片 [http://wiki.python.org/moin/HowTo/Sorting#head-194f75a6010b83e8ecbe30d3541d0f96455d6323 Sort wiki].[http://wiki.python.org/moin/HowTo/Sorting#head-194f75a6010b83e8ecbe30d3541d0f96455d6323 ]

循环

比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用:

   1 import string
   2 newL = []
   3 for s in l: newL.append( string.upper(s) )

用map就可以省去for循环的前头:

   1 import string
   2 newL = map (string.upper, l)

Guido的文章讲得很详细.

局域变量 及 '.'

象上面, 若用 append = newL.append, 及换种import方法:

   1 import string
   2 append = newL.append
   3 for s in l: append (string.upper(s))

会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找.

俺自己就不比较时间了, Skip Montanaro的结果是:

基本循环: 3.47秒
去点用局域变量: 1.79秒
使用map: 0.54秒

try的使用

比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会:

   1 count = {}
   2 for s in l:
   3     if not count.has_key(s): count[s] = 0
   4     else: count[s] += 1

由于每次都得在count中寻找是否已有同名关键词, 会很费时间. 而用try:

   1 count ={}
   2 for s in l:
   3     try: count[s] += 1
   4     except KeyError: count[s] = 0

就好得多. 当然若经常出现例外时, 就不要用try了.

import语句

这好理解. 就是避免在函数定义中来import一个模块, 应全在 全局块中来import

大量数据处理

由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应:

   1 def f():
   2 for d in hugeData: ...
   3 f()

而不要:

   1 def f(d): ...
   2 for d in hugeData: f(d)

这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对 解释性语言就更重要了.

减少周期性检查

这是Python的本征功能: 周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理.

可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔.

缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次.

当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著.

---编/译完毕. 看来Python是易学难精了, 象围棋?

我们自个儿的体悟

请有心得者分享!

在“大量数据处理”小节里,是不是说,不要再循环体内部调用函数,应该把函数放到外面?

从Python2.2开始,"找出速度瓶颈",已经可以使用hotshot模块了.据说对程序运行效率的影响要比profile小. -- jacobfan

"由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: " 这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan

数组排序中讲的方法真的会快点吗? 真的快到我们值得放弃直接用sort得到得可读性吗?值得怀疑 -- hoxide

PyOptimize (last edited 2009-12-25 07:10:04 by localhost)