首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python性能特征

Python性能特征
EN

Stack Overflow用户
提问于 2009-12-16 10:58:41
回答 6查看 2.1K关注 0票数 15

我正在调整我的一个宠物项目,以提高它的性能。我已经使用了分析器来识别热点,但是我认为更好地理解Pythons的性能特性将是非常有用的。

有几件事我想知道:

它的优化器有多聪明?

一些现代编译器拥有非常聪明的优化器,这些优化器通常可以使用简单的代码,并使其运行速度比任何人为优化代码的尝试都要快。根据优化器的智能程度,我的代码最好是“哑”。

虽然Python是一种“解释”语言,但它似乎可以编译成某种形式的字节码(.pyc)。当它这样做的时候它有多聪明?

  • 它会折叠常数吗?
  • 它是内嵌小功能还是展开短循环?
  • 它会执行复杂的数据/流程分析吗?我不具备正确解释的资格。

以下操作的速度(相比较)如何?

  • 函数调用
  • 类实例化
  • 算术
  • ‘'Heavier’数学运算,如sqrt()

数字是如何在内部处理的?

如何在Python中存储数字。它们是在内部存储为整数/浮动,还是以字符串的形式移动?

NumPy

NumPy能产生多大的性能差异?该应用程序充分利用了矢量和相关数学。通过使用它来加速这些操作,可以产生多大的差异。

其他有趣的事

如果你能想到任何值得知道的事情,那就随便提一下吧。

一些背景..。

由于有几个人提出了‘先看你的算法’的建议(这是相当明智的建议,但对我问这个问题的目的并没有帮助),我将在这里补充一些关于发生了什么,以及为什么我要问这个问题。

讨论中的宠物项目是用Python编写的射线跟踪器。它还不是很远,目前只对场景中的两个对象(一个三角形和一个球体)进行测试。没有阴影,阴影或照明计算正在进行。该算法基本上是:

代码语言:javascript
复制
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.

光线跟踪中的算法改进通常是通过早期消除场景中的对象来实现的。它们可以为复杂的场景提供相当大的推动力,但是如果这种射线追踪器不能在没有挣扎的情况下对一个仅仅两个物体进行测试,那么它将无法处理太多的问题。

虽然我意识到基于Python的射线追踪器无法达到基于C的射线跟踪器的性能,但考虑到像阿劳纳这样的实时射线跟踪器可以在我的计算机上管理15-20 FPS,在640x480的情况下绘制相当复杂的场景,我希望用Python渲染一个非常基本的500x500图像可以在一秒钟内完成。

目前,我的代码需要38秒。在我看来,这真的不应该花那么长时间。

分析显示了在这些形状的实际命中测试例程中花费的大部分时间。这在射线追踪仪和我所期望的范围内并不特别令人惊讶。这些命中测试的呼叫计数是每个250,000 (准确地说是500x500),这表明它们被调用的频率和它们应该的频率一样高。这是一个很好的教科书案例,3%的优化是可取的。

我计划在改进代码的同时,完成完整的计时/测量工作。然而,如果不了解Python的代价,我调优代码的尝试只不过是在黑暗中绊倒而已。我想,获得一点知识来照亮道路会对我有好处。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2009-12-16 16:40:18

Python的编译器是刻意的--简单--这使得它快速且高度可预测。除了一些常量的折叠,它基本上产生字节码,忠实地模仿你的来源。其他人已经建议使用dis,这确实是查看您正在获得的字节码的一个很好的方法--例如,for i in [1, 2, 3]:实际上不是在进行常量折叠,而是动态生成文字列表,而for i in (1, 2, 3): (在文字元组上循环而不是在文本列表上循环)是能够常量折叠的(原因是:列表是一个可变的对象,并保留“污点-简单”的任务语句,编译器不会费心检查这个特定列表是否被修改,这样就可以优化成一个元组)。

所以有足够的空间进行大量的手动微调--尤其是提升。即重写

代码语言:javascript
复制
for x in whatever():
    anobj.amethod(x)

作为

代码语言:javascript
复制
f = anobj.amethod
for x in whatever():
    f(x)

为了节省重复的查找(编译器不检查anobj.amethod的运行是否真的可以更改anobj的绑定&c,以便下次需要新的查找--它只是做了一件非常简单的事情,即没有提升,这可以保证正确性,但肯定不能保证速度)。

时差模块(最好在shell提示符IMHO中使用)使度量编译+字节码解释的总体效果变得非常简单(只需确保您正在测量的代码段不会产生影响定时的副作用,因为timeit 确实会在一个循环中反复运行它;-)。例如:

代码语言:javascript
复制
$ 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

您可以看到重复列表构建的成本--并且通过尝试一个小的调整来确认这确实是我们所观察到的:

代码语言:javascript
复制
$ 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

将迭代器的构造移到-s设置(只运行一次而不是计时)显示,循环本身在元组上稍微快一些(可能10%),但是第一对(列表比元组慢100%以上)的主要问题是构造。

有了timeit和编译器在优化过程中刻意简单的思想,我们可以很容易地回答您的其他问题:

以下操作的速度(相比较)如何? *函数调用*类实例化*算术*‘更重’的数学操作,如sqrt()

代码语言:javascript
复制
$ 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

因此我们看到:实例化一个新类型的类和调用一个函数(都是空的)的速度大致相同,实例化的速度可能很小,可能只有5%;实例化一个旧的类是最慢的(大约50%)。微小的差异,如5%或更少,当然可能是噪音,所以每次重复几次是可取的,但50%这样的差异肯定远远超出了噪音。

代码语言:javascript
复制
$ 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

这里我们看到:调用sqrt比通过操作符(使用**提升到幂运算符)进行相同的计算要慢得多,其代价大约是调用一个空函数;所有算术运算符在噪声范围内的速度大致相同(3或4纳秒的微小差绝对是噪声;-)。检查常量折叠是否会干扰:

代码语言:javascript
复制
$ 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

...we发现情况确实如此:如果将其中一个或两个数字都作为变量(这将阻止常量折叠)查找,我们将支付“现实”成本。变量查找有它自己的成本:

代码语言:javascript
复制
$ python -mtimeit -s'a=1.2; b=0.5' 'a'
10000000 loops, best of 3: 0.039 usec per loop

当我们试图测量如此微小的时间时,这是不可忽略的。事实上,常量查找也不是免费的:

代码语言:javascript
复制
$ python -mtimeit -s'a=1.2; b=0.5' '1.2'
10000000 loops, best of 3: 0.0225 usec per loop

正如你所看到的,虽然比变量查找更小,但它是相当可比的--大约一半。

如果和当你(仔细分析和测量)你决定你的计算的某些核心迫切需要优化的时候,我建议尝试cython --它是一个C / Python的合并,它试图和Python一样整洁,速度和C一样快,虽然它不能达到100%,但它肯定是一个很好的优势(特别是,它使得二进制代码比它的前身语言皮雷克斯更快,而且比它更丰富一些)。对于最后几%的性能,您可能仍然希望深入到C(或在某些特殊情况下的汇编/机器代码),但这将是非常非常罕见的。

票数 24
EN

Stack Overflow用户

发布于 2009-12-16 11:40:45

S.Lott是对的:最大的影响是数据结构和算法。另外,如果你做了大量的I/O,你如何管理它将产生很大的影响。

但是,如果您对编译器的内部结构感到好奇:它会折叠常量,但不会内联函数或展开循环。内联函数是动态语言中的一个难题。

您可以通过反汇编一些已编译的代码来查看编译器所做的工作。将一些示例代码放入my_file.py中,然后使用:

代码语言:javascript
复制
python -m dis my_file.py

这一消息来源:

代码语言:javascript
复制
def foo():
    return "BAR!"

for i in [1,2,3]:
    print i, foo()

生产:

代码语言:javascript
复制
  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

请注意,只有模块中的顶级代码被解压缩,如果您希望看到函数定义也被解压缩,您需要自己编写更多的代码来通过嵌套的代码对象进行递归。

票数 6
EN

Stack Overflow用户

发布于 2009-12-16 11:50:37

使用精神科模块可以自动提高代码的速度。

对于Numpy来说,它的速度通常是一个重要的因素。我认为在处理数字数组时必须这样做。

您还可能希望使用Cython皮雷克斯来加快代码的关键部分,这样您就可以创建更快的扩展模块,而不必用C编写完整的扩展模块(这会更麻烦)。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1913906

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档