Ada*_*ers 14 python performance
我正在调整我的宠物项目以改善其性能.我已经淘汰了剖析器来识别热点,但是我认为理解Pythons的性能特性会更好一些,这对今后非常有用.
我想知道一些事情:
一些现代编译器拥有非常聪明的优化器,通常可以使用简单的代码并使其运行速度比任何人类调整代码的尝试都要快.根据优化器的智能程度,我的代码可能要好得多"哑".
虽然Python是一种"解释"语言,但它确实可以编译成某种形式的字节码(.pyc).这样做有多聪明?
如何在Python中存储数字.它们是在内部存储为整数/浮点数还是作为字符串移动?
NumPy可以带来多少性能差异?该应用程序大量使用向量和相关数学.使用它来加速这些操作可以产生多大的差异.
如果您能想到任何值得了解的事情,请随时提及.
因为有一些人引入了"先看看你的算法"的建议(这是非常明智的建议,但对于我提出这个问题的目的并没有真正的帮助)我会在这里添加一些关于最新情况,为什么我要问这个问题.
有问题的宠物项目是用Python编写的光线追踪器.它还不是很远,目前只是针对场景中的两个对象(三角形和球形)进行测试.没有执行阴影,阴影或光照计算.算法基本上是:
for each x, y position in the image:
create a ray
hit test vs. sphere
hit test vs. triangle
colour the pixel based on the closest object, or black if no hit.
Run Code Online (Sandbox Code Playgroud)
光线跟踪中的算法改进通常通过尽早消除场景中的对象来工作.它们可以为复杂的场景提供相当大的提升,但是如果这个光线跟踪器无法在没有挣扎的情况下对仅仅两个物体进行测试,那么它根本无法处理.
虽然我意识到基于Python的光线跟踪器不能完全达到基于C的光线跟踪器的性能,因为像Arauna这样的实时光线跟踪器可以在我的计算机上管理15-20 FPS,渲染640x480的相当复杂的场景,我希望在Python中渲染一个非常基本的500x500图像可以在一秒钟内完成.
目前,我的代码需要38秒.在我看来,它真的不应该花那么长时间.
分析显示了在这些形状的实际命中测试例程中花费的大部分时间.这在光线追踪器中并不是特别令人惊讶,也是我所期待的.这些命中测试的呼叫计数分别为250,000(精确到500x500),这表明它们的调用次数与它们应该的频率相同.这是3%的漂亮教科书案例,其中建议进行优化.
当我努力改进代码时,我正计划进行完整的计时/测量.然而,如果没有一些关于Python成本的基本知识,那么我调试代码的尝试只会在黑暗中磕磕绊绊.我认为获得一点点知识来照亮我的方式对我很有帮助.
Ale*_*lli 23
Python的编译器故意简单 - 这使得它快速且高度可预测.除了一些常量折叠,它基本上生成字节码,忠实地模仿你的来源.其他人已经建议使用dis,这确实是查看你得到的字节码的一种好方法 - 例如,如何for i in [1, 2, 3]:实际上不进行常量折叠而是动态生成文字列表,同时for i in (1, 2, 3):(循环使用文字元组)而不是文字列表)是能够恒定倍(原因:一个列表是可变对象,并保持了"污垢简单的"任务书的编译器不会刻意去检查该特定列表从未如此修改它可以优化为元组).
因此,有足够的空间进行充分的手动微量优化 - 特别是提升.即,重写
for x in whatever():
anobj.amethod(x)
Run Code Online (Sandbox Code Playgroud)
如
f = anobj.amethod
for x in whatever():
f(x)
Run Code Online (Sandbox Code Playgroud)
保存重复的查找(编译器不检查是否anobj.amethod可以实际更改anobj的绑定和c,以便下次需要重新查找 - 它只是简单的事情,即没有提升,这保证正确但绝对不能保证超速;-).
该timeit模块(在shell提示下恕我直言,最好使用),使得它非常简单的测量汇编+字节码解释的总体影响(只是确保你测量有没有副作用会影响时序的片断,因为timeit 不跑过来在一个循环中;-).例如:
$ python -mtimeit 'for x in (1, 2, 3): pass'
1000000 loops, best of 3: 0.219 usec per loop
$ python -mtimeit 'for x in [1, 2, 3]: pass'
1000000 loops, best of 3: 0.512 usec per loop
Run Code Online (Sandbox Code Playgroud)
你可以看到重复列表构建的成本 - 并通过尝试小调整确认这确实是我们所观察到的:
$ python -mtimeit -s'Xs=[1,2,3]' 'for x in Xs: pass'
1000000 loops, best of 3: 0.236 usec per loop
$ python -mtimeit -s'Xs=(1,2,3)' 'for x in Xs: pass'
1000000 loops, best of 3: 0.213 usec per loop
Run Code Online (Sandbox Code Playgroud)
将iterable的构造移动到-s设置(仅运行一次而不是定时)表明循环正确在元组上可能稍快(可能是10%),但是第一对的大问题(列表比元组慢100%以上) )主要是建筑.
有了timeit编译器在其优化过程中故意非常简单的知识,我们可以轻松回答您的其他问题:
以下操作的速度有多快(相对而言)
Run Code Online (Sandbox Code Playgroud)* Function calls * Class instantiation * Arithmetic * 'Heavier' math operations such as sqrt()
$ python -mtimeit -s'def f(): pass' 'f()'
10000000 loops, best of 3: 0.192 usec per loop
$ python -mtimeit -s'class o: pass' 'o()'
1000000 loops, best of 3: 0.315 usec per loop
$ python -mtimeit -s'class n(object): pass' 'n()'
10000000 loops, best of 3: 0.18 usec per loop
Run Code Online (Sandbox Code Playgroud)
所以我们看到:实例化一个新式的类并且调用一个函数(都是空的)的速度大致相同,实例化的速度可能很小,可能是5%; 实例化旧式类的速度最慢(约50%).当然,5%或更小的微小差异可能是噪音,因此建议重复尝试几次; 但是像50%这样的差异肯定远远超出了噪音.
$ python -mtimeit -s'from math import sqrt' 'sqrt(1.2)'
1000000 loops, best of 3: 0.22 usec per loop
$ python -mtimeit '1.2**0.5'
10000000 loops, best of 3: 0.0363 usec per loop
$ python -mtimeit '1.2*0.5'
10000000 loops, best of 3: 0.0407 usec per loop
Run Code Online (Sandbox Code Playgroud)
在这里我们看到:调用sqrt比运算符(使用**提升功率运算符)大致调用空函数的成本进行相同的计算要慢; 所有算术运算符的速度与噪声内的速度大致相同(3或4纳秒的微小差异绝对是噪声;-).检查恒定折叠是否会干扰:
$ python -mtimeit '1.2*0.5'
10000000 loops, best of 3: 0.0407 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' 'a*b'
10000000 loops, best of 3: 0.0965 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' 'a*0.5'
10000000 loops, best of 3: 0.0957 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' '1.2*b'
10000000 loops, best of 3: 0.0932 usec per loop
Run Code Online (Sandbox Code Playgroud)
......我们确实看到了这种情况:如果将其中一个或两个数字作为变量查找(阻止常数折叠),我们就要支付"实际"成本.变量查找有其自己的成本:
$ python -mtimeit -s'a=1.2; b=0.5' 'a'
10000000 loops, best of 3: 0.039 usec per loop
Run Code Online (Sandbox Code Playgroud)
当我们试图测量如此微小的时间时,这远非微不足道.实际上,常量查找也不是免费的:
$ python -mtimeit -s'a=1.2; b=0.5' '1.2'
10000000 loops, best of 3: 0.0225 usec per loop
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,虽然小于变量查找,但它具有相当的可比性 - 大约一半.
如果(在经过仔细的分析和测量的情况下)你决定你的计算的某些核心迫切需要优化,我建议尝试cython - 这是一个C/Python合并,试图像Python一样整洁,和C一样快,而它无法达到100%它肯定会成为它的一个好拳头(特别是,它使二进制代码比你的前任语言,pyrex,以及比它更丰富得多一点快) .对于最后几个%的性能,您可能仍然希望转到C(或者在某些特殊情况下的汇编/机器代码),但这确实非常罕见.
S.Lott是对的:最重要的影响是数据结构和算法.此外,如果您正在进行大量I/O,那么您如何管理它将产生很大的不同.
但是如果你对编译器内部结构很好奇:它会折叠常量,但它不会内联函数或展开循环.内联函数是动态语言中的难题.
您可以通过反汇编一些编译代码来查看编译器的功能.在my_file.py中放入一些示例代码,然后使用:
python -m dis my_file.py
Run Code Online (Sandbox Code Playgroud)
这个来源:
def foo():
return "BAR!"
for i in [1,2,3]:
print i, foo()
Run Code Online (Sandbox Code Playgroud)
生产:
1 0 LOAD_CONST 0 (<code object foo at 01A0B380, file "\foo\bar.py", line 1>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (foo)
4 9 SETUP_LOOP 35 (to 47)
12 LOAD_CONST 1 (1)
15 LOAD_CONST 2 (2)
18 LOAD_CONST 3 (3)
21 BUILD_LIST 3
24 GET_ITER
>> 25 FOR_ITER 18 (to 46)
28 STORE_NAME 1 (i)
5 31 LOAD_NAME 1 (i)
34 PRINT_ITEM
35 LOAD_NAME 0 (foo)
38 CALL_FUNCTION 0
41 PRINT_ITEM
42 PRINT_NEWLINE
43 JUMP_ABSOLUTE 25
>> 46 POP_BLOCK
>> 47 LOAD_CONST 4 (None)
50 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
请注意,只有模块中的顶级代码被反汇编,如果要查看反汇编的函数定义,则需要自己编写更多代码来通过嵌套代码对象进行递归.
| 归档时间: |
|
| 查看次数: |
1915 次 |
| 最近记录: |