我是Python的长期用户。几年前,我开始学习C ++,以了解它在速度方面可以提供什么。在这段时间里,我将继续使用Python作为原型制作工具。看来,这是一个很好的系统:使用Python进行敏捷开发,在C ++中快速执行。
最近,我越来越多地使用Python,并学习如何避免早年使用该语言时很快使用的所有陷阱和反模式。据我了解,使用某些功能(列表理解,枚举等)可以提高性能。
但是,是否存在技术限制或语言功能使我的Python脚本无法与同等C ++程序一样快?
我是Python的长期用户。几年前,我开始学习C ++,以了解它在速度方面可以提供什么。在这段时间里,我将继续使用Python作为原型制作工具。看来,这是一个很好的系统:使用Python进行敏捷开发,在C ++中快速执行。
最近,我越来越多地使用Python,并学习如何避免早年使用该语言时很快使用的所有陷阱和反模式。据我了解,使用某些功能(列表理解,枚举等)可以提高性能。
但是,是否存在技术限制或语言功能使我的Python脚本无法与同等C ++程序一样快?
Answers:
几年前我从事全职Python编程工作时,自己有点碰壁。我确实喜欢Python,但是当我开始进行一些性能调整时,我受到了一些粗鲁的冲击。
严格的Pythonista使用者可以纠正我的问题,但是以下是我发现的内容,这些内容以非常广泛的笔触进行了描绘。
这会影响性能,因为与其他语言相比,这意味着在运行时还会有更多级别的间接访问,此外还会浪费大量内存。
其他人可以与执行模型对话,但是Python是在运行时进行编译然后进行解释的,这意味着它并不能一直用于机器代码。这也会对性能产生影响。您可以轻松地链接C或C ++模块,或者找到它们,但是如果您直接运行Python,它将对性能产生很大的影响。
现在,在Web服务基准测试中,Python比其他运行时编译语言(如Ruby或PHP)优越。但这远远落后于大多数编译语言。即使是编译成中间语言并在VM中运行的语言(如Java或C#),也要好得多。
这是我偶尔提到的一组非常有趣的基准测试:
http://www.techempower.com/benchmarks/
(尽管如此,我仍然非常喜欢Python,如果我有机会选择自己使用的语言,那将是我的第一选择。在大多数情况下,无论如何我都不受疯狂的吞吐量要求的约束。)
__slots__
。PyPy在这方面应该做得更好,但我还不足以判断。
Python参考实现是“ CPython”解释器。它尝试相当快,但目前未采用高级优化。对于许多使用场景而言,这是一件好事:对某些中间代码的编译会在运行时之前立即进行,并且每次执行程序时都会重新编译一次代码。因此,必须将优化所需的时间与优化所获得的时间进行权衡–如果没有净收益,则优化毫无价值。对于运行时间非常长的程序或循环非常紧密的程序,采用高级优化将很有用。但是,CPython用于某些无法进行积极优化的工作:
短运行脚本,例如用于sysadmin任务。像Ubuntu这样的许多操作系统都在Python的基础上构建了很大一部分基础架构:CPython足够快地完成这项工作,但是实际上没有启动时间。只要它比bash快,那就好。
CPython必须具有清晰的语义,因为它是参考实现。这允许进行简单的优化,例如“优化foo运算符的实现”或“将列表理解编译为更快的字节码”,但通常会排除破坏信息的优化,例如内联函数。
当然,除了CPython之外,还有更多的Python实现:
Jython构建在JVM之上。JVM可以解释或JIT编译提供的字节码,并具有配置文件引导的优化。它的启动时间很长,而且要花一点时间才能启动JIT。
PyPy是最先进的JITting Python VM。PyPy用RPython(Python的受限子集)编写。该子集从Python中删除了一些表达性,但允许静态推断任何变量的类型。然后,可以将用RPython编写的VM转换为C,从而获得与RPython类似的C性能。但是,RPython仍比C具有更高的表现力,从而可以更快地开发新的优化程序。PyPy是编译器自举的一个示例。PyPy(不是RPython!)主要与CPython参考实现兼容。
Cython(与RPython一样)是具有静态类型的不兼容Python方言。它还可以转换为C代码,并且能够轻松地为CPython解释器生成C扩展。
如果您愿意将Python代码转换为Cython或RPython,那么您将获得类似C的性能。但是,不应将它们理解为“ Python的子集”,而应理解为“具有Pythonic语法的C”。如果您切换到PyPy,您的原始Python代码将获得可观的速度提升,但也将无法与用C或C ++编写的扩展接口。
但是,除了启动时间长之外,还有哪些属性或功能会阻止香草Python达到类似C的性能水平?
贡献者和资金。与Java或C#不同,在该语言的背后没有一家独立的驱动公司有兴趣使该语言成为同类产品中的佼佼者。这将发展限制在主要限于志愿者和偶尔的赠款上。
后期绑定和缺少任何静态类型。Python允许我们这样编写废话:
import random
# foo is a function that returns an empty list
def foo(): return []
# foo is a function, right?
# this ought to be equivalent to "bar = foo"
def bar(): return foo()
# ooh, we can reassign variables to a different type – randomly
if random.randint(0, 1):
foo = 42
print bar()
# why does this blow up (in 50% of cases)?
# "foo" was a function while "bar" was defined!
# ah, the joys of late binding
在Python中,可以随时重新分配任何变量。这样可以防止缓存或内联;任何访问都必须通过变量。这种间接影响了性能。当然:如果您的代码没有做这些疯狂的事情,以便可以在编译之前为每个变量指定确定的类型,并且每个变量只分配一次,那么从理论上讲,可以选择一个更有效的执行模型。考虑到这一点的语言将提供一些将标识符标记为常量的方法,并且至少允许使用可选的类型注释(“渐进式键入”)。
可疑的对象模型。除非使用插槽,否则很难确定对象具有哪些字段(Python对象本质上是字段的哈希表)。即使到了那里,我们仍然不知道这些字段的类型。这样可以防止将对象表示为紧密打包的结构,就像C ++中那样。(当然,C ++的对象表示也不理想:由于类似结构的性质,即使私有字段也属于对象的公共接口。)
垃圾收集。在许多情况下,可以完全避免使用GC。C ++允许我们静态分配对象,这些对象在离开当前范围时会自动销毁:Type instance(args);
。在此之前,该对象是活动的,可以借给其他功能。这通常是通过“引用传递”完成的。诸如Rust之类的语言允许编译器静态地检查指向该对象的指针是否没有超出该对象的寿命。这种内存管理方案是完全可预测的,高效的,并且适合大多数情况下没有复杂对象图的情况。不幸的是,Python在设计时并未考虑内存管理。从理论上讲,逸出分析可用于查找可以避免GC的情况。实际上,简单的方法链例如foo().bar().baz()
将必须在堆上分配大量的短期对象(世代GC是使此问题保持较小的一种方法)。
在其他情况下,程序员可能已经知道某些对象(如列表)的最终大小。不幸的是,Python在创建新列表时没有提供传达此信息的方法。相反,新项目将被推送到最后,这可能需要多个重新分配。一些注意事项:
可以创建特定大小的列表,例如fixed_size = [None] * size
。但是,该列表中对象的内存将必须单独分配。对比C ++,我们可以做到std::array<Type, size> fixed_size
。
可以通过array
内置模块在Python中创建特定本机类型的压缩数组。此外,numpy
还提供了具有本机数字类型特定形状的数据缓冲区的有效表示。
Python被设计为易于使用,而非性能。它的设计使创建高效的实现变得相当困难。如果程序员放弃了有问题的功能,那么理解其余习语的编译器将能够发出可与C媲美的高效代码。
影响所有动态语言性能的三个主要因素比其他因素更为重要。
对于C / C ++,这三个因素的相对成本几乎为零。指令直接由处理器执行,调度最多需要一个或两个间接调用,除非您这样说,否则永远不会分配堆内存。编写良好的代码可能会采用汇编语言。
对于使用JIT编译的C#/ Java,前两个比较低,但是垃圾回收的内存成本很高。编写良好的代码可能接近2倍的C / C ++。
对于Python / Ruby / Perl,这三个因素的成本都相对较高。认为是C / C ++的5倍或更差。(*)
请记住,运行时库代码很可能用与您的程序相同的语言编写,并且具有相同的性能限制。
(*)随着Just-In_Time(JIT)编译扩展到这些语言,它们也将接近(通常为2倍)编写良好的C / C ++代码的速度。
还应注意的是,一旦差距缩小(在竞争的语言之间),差异就由算法和实现细节主导。JIT代码可能胜过C / C ++,而C / C ++可能胜过汇编语言,因为编写好代码更容易。
Hash
类(Ruby的核心数据结构之一)是用Ruby编写的,它的性能与Hash
用C编写的YARV的类相当,有时甚至更快。原因之一是Rubinius的大部分运行时系统是用Ruby编写的,让他们可以...
但是,是否存在技术限制或语言功能使我的Python脚本无法与同等C ++程序一样快?
否。这只是投入大量资金和资源以使C ++快速运行与投入资金和资源以使Python快速运行有关的问题。
例如,当自助虚拟机问世时,它不仅是最快的动态OO语言,而且是最快的OO语言时期。尽管它是一种令人难以置信的动态语言(例如,比Python,Ruby,PHP或JavaScript还要多),但是它比大多数可用的C ++实现要快。
但是随后Sun取消了Self项目(一种用于开发大型系统的成熟的通用OO语言),而只专注于电视机顶盒中的动画菜单的小型脚本语言(您可能已经听说过,称为Java),当时没有更多资金。同时,英特尔,IBM,微软,Sun,Metrowerks,HP等。花了大量的金钱和资源来提高C ++的速度。CPU制造商在其芯片中添加了功能,以加快C ++的速度。编写或修改了操作系统,以使C ++更快。因此,C ++速度很快。
我不是非常熟悉Python,我是Ruby的人,所以我将举一个Ruby的例子:Rubinius Ruby实现中的Hash
类(在功能和重要性上dict
与Python 等效)是用100%纯Ruby编写的;但是它的竞争非常好,有时甚至胜过Hash
用手工优化的C语言编写的YARV中的类。并且与某些商用的Lisp或Smalltalk系统(或前面提到的Self VM)相比,Rubinius的编译器甚至都不那么聪明。
Python没有内在的特性可以使它变慢。当今的处理器和操作系统中有一些功能会损害Python(例如,众所周知,虚拟内存对于垃圾收集性能非常糟糕)。有一些功能可以帮助C ++但不能帮助Python(现代CPU试图避免高速缓存未命中,因为它们是如此昂贵。不幸的是,当您拥有OO和多态性时,避免高速缓存未命中很难。相反,您应该降低高速缓存的成本专为Java设计的Azul Vega CPU做到了这一点。
如果您像在C ++上一样花大量的金钱,研究和资源来使Python快速运行,而在使像Python上那样快速运行Python的操作系统上花费了同样的钱,研究和资源,那么您就花了在使CPU能够像C ++一样快速运行的CPU方面投入了大量金钱,研究和资源,因此,毫无疑问,Python可以达到与C ++相当的性能。
我们已经用ECMAScript看到了如果只有一名玩家认真对待性能会发生什么。一年之内,我们所有主要供应商的整体性能基本上提高了10倍。