Python的基本优化模式(python -O)有什么用途?

53

Python有一个标记-O,可以用它来执行解释器。该选项将生成“优化”的字节码(写入.pyo文件),如果给定两次,则会丢弃文档字符串。根据Python的手册:

  

-O打开基本优化。这将更改已编译(字节码)文件的文件名扩展名从.pyc到.pyo。重复给定两次,会导致丢弃文档字符串。

我认为此选项的两个主要功能是:

  • 剥离所有assert语句。这是为了换取速度而牺牲防御损坏程序状态的能力。但是,您需要大量assert语句才能使其产生区别吗?您是否有任何代码在这方面具有价值(并且健全?)

  • 删除所有文档字符串。在哪种应用程序中,内存使用如此关键,这是一种胜利?为什么不将所有内容推入C模块中编写?

这个选项的用途是什么? 它是否具有现实世界的价值?


11
你可以使用它来翻转测试套件上的闪烁指示灯,使它们偷偷地忽略断言。喝彩!你完成了这个项目!(注意:不要这样做) - Shayne
7个回答

51
另一个使用-O标志的用途是将__debug__内置变量的值设置为False。因此,基本上,您的代码可以有很多“调试”路径,比如:
if __debug__:
     # output all your favourite debugging information
     # and then more

当使用-O运行时,该代码甚至不会被包含在.pyo文件中作为字节码;这是一种类似于C语言的低配版#ifdef。

请记住,只有在标志为-OO时,才会删除文档字符串。


24
哇哦,我原以为你想知道这个选项的真实世界用途。谢谢你发现我的回答几乎毫无用处。顺便说一下,如果你想让某个人为 Guido 和 Python 核心团队的选择进行辩解,你不应该在这里提问;最后,你可以依赖于特定模式的使用,程序员可以控制是否使用优化;在 Stack Overflow 上问一个相关问题就行了。 我在此宣布你的假设基本上是错误的,而我的时间已经浪费了。 干杯。 很抱歉让你失望。 - tzot
3
我很高兴在Stack Overflow上进行对话,因此我没有理由对获得许多答案感到失望。我的意思是我说的话都是真心的,但我是在谈论你展示的例子。事实是,无论是你展示它还是你本人,都不会受到负面评价。请注意,我的翻译尽力保持原文意思和风格,同时尽量使语言通俗易懂。 - u0b34a0f6ae
1
python-ldap使用__debug__,它控制是否使用调试跟踪语句逻辑。公平地说,针对__debug__进行检查比针对内存中的本地值进行哈希查找,然后再进行另一个哈希查找以查看它是否为调试模式要快得多。但是,由于大多数人通常不会创建pyo文件,因此通常不需要担心__debug__,而应该有另一种方式来实现调试/非调试模式。 - JamesHutchison
3
顺便说一下,许多现实世界中的开源框架已经利用了 __debug__,包括 distlib、html5lib、IPython、Jinja2、matplotlib、python-ldap、speechd 和太多官方 CPython stdlib 模块以计数(例如,imaplib、pickletools、statistics、unittest)。__debug__ 绝对有其存在的意义。老实说,我希望看到更多地利用它。 - Cecil Curry
1
@CecilCurry:lilydjwg说他们“还没有看到人们在实际代码中使用__debug__”,所以这是主观的,这就是为什么我从未回答的原因。无论如何,我经常发现使用自己缺乏经验(“我从未经历过…”)作为反对事实的论点(在这种情况下:__debug__的实际用途)是一种替代方式,即“我不喜欢它,所以你也不应该喜欢”。 - tzot
显示剩余2条评论

33

关于去除 assert 语句:这在C语言中是一个标准选项,许多人认为ASSERT的定义的一部分是它不会在生产代码中运行。无论是否去除assert语句会有所区别,取决于这些语句的作用,而非它们的数量。

def foo(x):
    assert x in huge_global_computation_to_check_all_possible_x_values()
    # ok, go ahead and use x...

当然,大多数断言并非如此,但重要的是要记住,你可以做出像这样的事情。

至于删除文档字符串,似乎是从更简单的时代延续下来的古怪习惯,不过我猜在内存受限的环境中可能会有所区别。


历史很重要,这是一个好观点。然而,我不想看到玩具示例,我想看看在现实世界的代码中使用了哪些断言,并且它是否有所区别。 - u0b34a0f6ae
24
内存速度的增长远远落后于CPU速度,特别是如果考虑我们不断添加更快的处理器而非内存带宽。因此,内存成为了新的磁盘,L2缓存成为了新的内存。而且,与内存相比,L2缓存实际上非常小,并且它们还在不断变小。(例如,Core2有6144KiB,而i7只有256KiB。)所以,再次开始计算字节数实际上变得有用了。 - Jörg W Mittag
2
OpenGL库,如PyOpenGL和pyglet,在运行时会执行一些非常昂贵的安全检查断言,除非您指定-O。 - Jonathan Hartley
1
如果您使用严格的契约式编程,您可能会在编写的每个函数开头和结尾都加入断言。 - DylanYoung

8

如果你的代码经常被调用(例如在内部循环中),则删除其中的断言可以显著提高性能。以下是一个极端的例子:

$ python    -c 'import timeit;print timeit.repeat("assert True")'
[0.088717937469482422, 0.088625192642211914, 0.088654994964599609]
$ python -O -c 'import timeit;print timeit.repeat("assert True")'
[0.029736995697021484, 0.029587030410766602, 0.029623985290527344]

在实际情况下,节省的量通常会少得多。

去掉文档字符串可能会减小代码的大小,从而减少工作集。

在许多情况下,性能影响将是可以忽略不计的,但像优化一样,确保的唯一方法是测量。


1
这个问题涉及到现实世界的代码。顺便说一下,这更加实用:python -mtimeit "" "assert(True)"(设置在第一个参数中)。 - u0b34a0f6ae
1
这个例子对我来说看起来有些奇怪。你把那些微不足道的代码减少到了不存在的代码,这并不能展示出实际速度上的提升。一个更为现实的用例是进行一项操作时需要做出很多代价高昂的假设,而你相信它们应该总是满足的。例如,如果我正试图返回抛物线的根,我可以检查 b*2 - 4a*c > 0,以确保存在实根,如果这是我感兴趣的。许多有用的公式有很多限制条件。 - Mike Graham
2
另外,assert是一种语句,我希望它像"assert True"这样使用,而不是assert(True)。当您添加消息时,这变得非常重要,因为assert a == b, "必须为真"assert(a == b, "必须为真")非常不同,特别是后者总是通过。 - Mike Graham
@kaizer.se:没有语句是第一个参数,设置是第二个参数;在您的示例中,assert将位于设置中,因此-O没有可衡量的效果。 - oefe
@Mike:当然很奇怪,因为大多数例子都被简化到了极致。基本上,优化版本的示例测量了timeit循环的开销,而未经优化的版本显示了assert本身的开销。实际节省可能更多或更少,这取决于哪个更昂贵:您的工作代码还是断言。通常,但并非总是,断言相对较为微不足道,因此可以声称通常节省较少。感谢您提醒括号的问题,我已将其删除! - oefe
@oefe:在命令行上,第一个参数是 setup,然后是 stmt。 - u0b34a0f6ae

7

我从未遇到过使用 -O 的好理由。我一直认为它的主要目的是为了以防将来添加了一些有意义的优化。


4
好的,它确实能做一些事情,只是通常这些东西并不是很有用。 - Mike Graham

7
“但是难道不需要大量的assert语句才能使这个方法有所不同吗?你有任何可行(且理智的)代码例子吗?”
“以获取图中节点之间路径为例,我有一段代码,在函数结尾处加了一个assert语句来检查路径是否包含重复项:”
assert not any(a == b for a, b in zip(path, path[1:]))

在开发过程中,我喜欢这个简单语句所带来的心灵平静和清晰度。在生产环境下,代码要处理一些大型图表,而这一行代码可能占据了运行时间的66%。因此,使用-O可以显著提高速度。


5

我想最常使用-O的用户是py2exepy2app等类似软件。

个人而言,我从未直接使用过-O


1
而且,为什么py2exe要使用它? - u0b34a0f6ae
5
创建独立可执行文件时,不需要文档字符串。它们只会占用内存空间。 - gnud

4
你已经基本了解了:它实际上几乎什么都不做。除非你的内存严重不足,否则你几乎永远看不到速度或内存方面的提升。

2
如果 debug 为真:r = long_running_function(); assert n - 0.01 < r; assert r < n + 0.01,测试启发式算法的容差(其中 n 是启发式算法的结果),通常在编程时非常有用,但在实际使用启发式算法时则无用(甚至可能会对真实数据造成伤害,并且可能永远无法完成计算)。因此,您可以将一个从不停止的函数转换为在毫秒内完成的函数。这听起来像是一个巨大的收益! - DylanYoung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接