为什么Python看起来平均比C/C++慢?我学习Python作为我的第一门编程语言,但是我刚开始学习C++就感觉到了明显的差异。
为什么Python看起来平均比C/C++慢?我学习Python作为我的第一门编程语言,但是我刚开始学习C++就感觉到了明显的差异。
Python是比C更高级的语言,这意味着它会将计算机的细节——如内存管理、指针等——抽象出来,让你可以以更接近人类思维方式的方式编写程序。
确实,仅考虑执行时间时,C代码通常比Python代码运行速度快10到100倍。然而,如果你还考虑开发时间,Python往往能够击败C。对于许多项目而言,开发时间比运行时间表现更为关键。较长的开发时间直接转化为额外的成本、更少的功能和缓慢的上市时间。
Python代码执行速度较慢的内部原因是因为代码在运行时进行解释,而不是在编译时被编译成本地代码。
其他解释型语言(如Java字节码和.NET字节码)比Python运行速度更快,因为标准发行版包括一个JIT编译器,该编译器在运行时将字节码编译为本地代码。CPython没有 JIT 编译器的原因是 Python 的动态性使得编写 JIT 编译器变得困难。目前有一些工作正在进行,以编写更快的 Python 运行时,因此您应该期望未来性能差距会缩小,但在标准的 Python 发行版中包含一个功能强大的 JIT 编译器之前,可能还需要一段时间。
CPython的速度较慢,因为它没有即时编译器(由于它是参考实现,在某些情况下选择简单而不是性能)。Unladen Swallow 是一个项目,旨在将基于LLVM的JIT添加到CPython中,并实现了大量的加速。Jython和IronPython可能也比CPython快得多,因为它们支持高度优化的虚拟机(JVM和.NET CLR)。
然而,一个显然会使Python变慢的因素是它是动态类型语言,并且每个属性访问都需要进行大量查找。
例如,在对象A上调用f
会导致可能在__dict__
中进行查找、调用__getattr__
等,最终调用可调用对象f
的__call__
方法。
关于动态类型,如果你知道你正在处理的数据类型,有许多优化可以完成。例如,在Java或C中,如果你有一个整数数组要求和,最终汇编代码可能只需要从索引i
处获取值,将其与累加器相加,然后递增i
。
在Python中,这很难使代码达到这种最佳状态。假设你有一个包含int
的列表子类对象。在添加之前,Python必须调用list.__getitem__(i)
,然后通过调用accumulator.__add__(n)
将其添加到“累加器”中,然后重复这个过程。这里可能会发生大量的备选查找,因为另一个线程可能已经更改了例如__getitem__
方法、列表实例的字典或类的字典等。甚至在本地命名空间中查找累加器和列表(以及任何你正在使用的变量)也需要进行字典查找。对于使用任何用户定义的对象,这种开销都适用,尽管对于一些内置类型来说,它会有所缓解。
sum(list)
而不是使用累加器和索引。坚持使用这些类型,并进行一些数字计算,例如使用 int/float/complex 类型,通常不会出现速度问题。如果确实存在性能瓶颈,则可能有一个关键的时间敏感单元(例如 SHA2 摘要函数),您可以将其简单地移植到 C 代码(或 Jython 中的 Java 代码)中。事实是,当您编写 C 或 C++ 代码时,您将浪费大量时间来完成您可以在几秒钟/几行 Python 代码中完成的事情。我认为这种权衡总是值得的,除非您正在做嵌入式或实时编程等无法承受的情况。编译与解释在这里并不重要:Python是经过编译的,对于任何非平凡程序来说,它只是运行时成本的一小部分。
主要成本包括:缺乏与本地整数相对应的整数类型(使所有整数操作变得更加昂贵)、缺乏静态类型(使方法解析更加困难,并意味着必须在运行时检查值的类型)以及缺乏未装箱的值(可以减少内存使用量,并避免一级间接性)。
并不是说这些事情在Python中不可能或不能更有效地实现,而是选择了方便和灵活性以及语言清晰度优先于运行时速度。一些聪明的JIT编译可能会克服其中一些成本,但Python提供的好处总是伴随着一些成本。
Python和C之间的区别通常是解释型(字节码)和编译型(本地代码)语言之间的区别。个人认为,我并不认为Python很慢,它表现得相当不错。如果您尝试在其领域之外使用它,当然它会变慢。但是,您可以为Python编写C扩展程序,将时间关键的算法放入本地代码中,从而使其更快。
C和C++编译成本地代码,也就是说它们直接在CPU上运行。Python是一种解释型语言,这意味着你编写的Python代码必须经过许多抽象阶段才能变成可执行的机器码。
#!/usr/bin/python3
# title : /var/www/cgi-bin/name2.py
# author: Neil Rieck
# edit : 2019-10-19
# ==================
import name3 # name3.py will be cache-checked and/or compiled
import name4 # name4.py will be cache-checked and/or compiled
import name5 # name5.py will be cache-checked and/or compiled
#
def main():
#
# code that uses the imported libraries goes here
#
if __name__ == "__main__":
main()
#
一旦执行完成,编译输出的代码将被丢弃。然而,如果您通过类似以下导入语句启动,则您的主要Python程序将被编译:
#!/usr/bin/python3
# title : /var/www/cgi-bin/name1
# author: Neil Rieck
# edit : 2019-10-19
# ==================
import name2 # name2.py will be cache-checked and/or compiled
#name2.main() #
现在说一下注意事项:
最后一点。您可以自己访问编译器,生成pyc文件,然后更改保护位以解决我列出的任何注意事项。以下是两个示例:
method #1
=========
python3
import py_compile
py_compile("name1.py")
exit()
method #2
=========
python3 -m py_compile name1.py