Size: 6172
Comment:
|
← Revision 18 as of 2009-12-25 07:10:04 ⇥
Size: 7705
Comment: converted to 1.6 markup
|
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用得很 好. |
Line 91: | Line 64: |
* Python2.4以后, sort和sorted的使用可以参考这片 Wiki: [[http://wiki.python.org/moin/HowTo/Sorting#head-194f75a6010b83e8ecbe30d3541d0f96455d6323|HowToSort]] === 循环 === 比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用: |
|
Line 92: | Line 68: |
=== 循环 === 比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用: |
|
Line 99: | Line 70: |
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 74: |
用map就可以省去for循环的前头: | 用map就可以省去for循环的前头: |
Line 106: | Line 78: |
import string newL = map (string.upper, l) |
import string newL = map (string.upper, l) |
Line 109: | Line 81: |
Guido的文章讲得很详细. | Guido的文章讲得很详细. |
Line 113: | Line 84: |
象上面, 若用 append = newL.append, 及换种import方法: | 象上面, 若用 append = newL.append, 及换种import方法: |
Line 116: | Line 88: |
import string append = newL.append for s in l: append (string.upper(s)) }}} 会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找. |
|
Line 117: | Line 94: |
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 101: |
=== try的使用 === 比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会: |
|
Line 122: | Line 104: |
会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找. 俺自己就不比较时间了, Skip Montanaro的结果是: {{{ 基本循环: 3.47秒 去点用局域变量: 1.79秒 使用map: 0.54秒 }}} === try的使用 === 比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会: |
|
Line 137: | Line 106: |
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 111: |
由于每次都得在count中寻找是否已有同名关键词, 会很费时间. 而用try: |
由于每次都得在count中寻找是否已有同名关键词, 会很费时间. 而用try: |
Line 146: | Line 115: |
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 120: |
就好得多. 当然若经常出现例外时, 就不要用try了. | 就好得多. 当然若经常出现例外时, 就不要用try了. |
Line 155: | Line 123: |
这好理解. 就是避免在函数定义中来import一个模块, 应全在 全局块中来import |
这好理解. 就是避免在函数定义中来import一个模块, 应全在 全局块中来import |
Line 160: | Line 126: |
由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: |
由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: |
Line 164: | Line 130: |
def f(): for d in hugeData: ... f() |
def f(): for d in hugeData: ... f() |
Line 168: | Line 134: |
而不要: | 而不要: |
Line 171: | Line 138: |
def f(d): ... for d in hugeData: f(d) |
def f(d): ... for d in hugeData: f(d) |
Line 174: | Line 141: |
这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对 解释性语言就更重要了. |
这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对 解释性语言就更重要了. |
Line 179: | Line 144: |
这是Python的本征功能: 周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理. |
这是Python的本征功能: 周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理. |
Line 182: | Line 146: |
可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔. | 可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔. |
Line 184: | Line 148: |
缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次. | 缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次. |
Line 186: | Line 150: |
当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著. | 当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著. |
Line 189: | Line 153: |
---编/译完毕. 看来Python是易学难精了, 象围棋? | ---编/译完毕. 看来Python是易学难精了, 象围棋? |
Line 191: | Line 155: |
Line 195: | Line 158: |
在“大量数据处理”小节里,是不是说,不要再循环体内部调用函数,应该把函数放到外面? | == 故事 == |
Line 197: | Line 160: |
从Python2.2开始,"找出速度瓶颈",已经可以使用hotshot模块了.据说对程序运行效率的影响要比profile小. -- jacobfan | * '''[[http://www.haokanbu.com/story/17930/|Python性能调优]]''' ~ [[http://www.haokanbu.com/user/11931/|flydudu]] 分享 |
Line 199: | Line 162: |
"由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: " 这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan |
== 思考 == * 在“大量数据处理”小节里,是不是说,不要再循环体内部调用函数,应该把函数放到外面?从Python2.2开始,"找出速度瓶颈",已经可以使用hotshot模块了.据说对程序运行效率的影响要比profile小. -- jacobfan * "由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: " 这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan * 数组排序中讲的方法真的会快点吗? 真的快到我们值得放弃直接用sort得到得可读性吗?值得怀疑 -- hoxide * Python2.4以后 sort和sorted的使用更加灵活,link已经加到文中,我没有比较过效率。-yichun * 关于 “try的使用”: 其实setdefault方法就是为这个目的设的: {{{ #!python count = {} for s in l: count.setdefault(s, 0) += 1 }}} 这个其实能做更多。通常遇到的问题是要把类似的东西group起来,所以你可能想用: {{{ #!python count = {} for s in l: count.setdefault(s, []).append(s) }}} 但是这样你只能把同样的东西hash起来,而不是一类东西。比如说你有一个dict构成的list叫sequence,需要按这些dict的某个key value分类,你还要对分类后的每个类别里面的这些dict各作一定的操作,你就需要用到Raymond实现的这个[[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/259173|groupby]],你就可以写: {{{ totals = dict((key, group) for key, group in groupby(sequence, lambda x: x.get('Age')))}}} - yichun ---- * shortStrs = [str(s) for s in shortStrs[i]]这句我在python2.5下报错(i未定义),我改成shortStrs = [str(s) for s in shortStrs]就可以了。 -wfifi |
Python编程速度技巧 -- Zoom.Quiet [2004-08-09 23:59:25]
Contents
[转载自oso,其上有“python编程简介”系列,这是第九篇,是篇译文] 有了前面的描述, 大家差不多知道什么是Python了. 最近因为在网 上看一两篇关于Python的运行速度问题, 不给大家介绍一下心里不 踏实,
Python编程速度技巧
最常见
- 一个最常见的速度陷坑(至少是俺在没看到网上这篇介绍时陷进去
过好些次的) 是: 许多短字串并成长字串时, 大家通常会用:
因为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
模块:
就会打印出那个函数里调用了几次其它函数, 各用了多少时间, 总共用了多少时间等信息 --- Nice ? 详请参阅<<库参考: invalid macro name>>中的 profile模块的论述.
当然脑袋笨一点或是聪明一点的, 也可以用time模块中的time() 来显示系统时间, 减去上次的time()就是与它的间隔秒数了.
字串相并
- 就头上的例子而言, 用 :
1 longStr =''.join(shortStrs)
立马搞定, 但如果shortStrs里面不都是字串, 而包含了些数 字呢 ? 直接用join就会出错. 不怕, 这样来:
也即先将数列中所有内容都转化为字串, 再用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的使用可以参考这片 Wiki: HowToSort
循环
比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用:
用map就可以省去for循环的前头:
Guido的文章讲得很详细.
局域变量 及 '.'
象上面, 若用 append = newL.append, 及换种import方法:
会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找.
俺自己就不比较时间了, Skip Montanaro的结果是:
基本循环: 3.47秒 去点用局域变量: 1.79秒 使用map: 0.54秒
try的使用
比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会:
由于每次都得在count中寻找是否已有同名关键词, 会很费时间. 而用try:
就好得多. 当然若经常出现例外时, 就不要用try了.
import语句
这好理解. 就是避免在函数定义中来import一个模块, 应全在 全局块中来import
大量数据处理
由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应:
而不要:
这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对 解释性语言就更重要了.
减少周期性检查
这是Python的本征功能: 周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理.
可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔.
缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次.
当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著.
---编/译完毕. 看来Python是易学难精了, 象围棋?
我们自个儿的体悟
请有心得者分享!
故事
Python性能调优 ~ flydudu 分享
思考
- 在“大量数据处理”小节里,是不是说,不要再循环体内部调用函数,应该把函数放到外面?从Python2.2开始,"找出速度瓶颈",已经可以使用hotshot模块了.据说对程序运行效率的影响要比profile小. -- jacobfan
- "由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: " 这句译文中,overhead翻译成"前头"好象不妥.翻译成"由于Python中函数调用的开销比较大,..."要好些 -- jacobfan
- 数组排序中讲的方法真的会快点吗? 真的快到我们值得放弃直接用sort得到得可读性吗?值得怀疑 -- hoxide
- Python2.4以后 sort和sorted的使用更加灵活,link已经加到文中,我没有比较过效率。-yichun
- 关于 “try的使用”:
其实setdefault方法就是为这个目的设的:
这个其实能做更多。通常遇到的问题是要把类似的东西group起来,所以你可能想用:
但是这样你只能把同样的东西hash起来,而不是一类东西。比如说你有一个dict构成的list叫sequence,需要按这些dict的某个key value分类,你还要对分类后的每个类别里面的这些dict各作一定的操作,你就需要用到Raymond实现的这个groupby,你就可以写:
totals = dict((key, group) for key, group in groupby(sequence, lambda x: x.get('Age')))
- yichun
- shortStrs = [str(s) for s in shortStrs[i]]这句我在python2.5下报错(i未定义),我改成shortStrs = [str(s) for s in shortStrs]就可以了。
-wfifi