Python当前人气暴涨。它在DevOps,数据科学,Web开发和安全领域均有使用。

但是在速度方面没有赢得美誉。

这里有关于Python比较其他语言如,Java, C#, Go, JavaScript, C++进行性能对比,其中Python是最慢的。包含了JIT(C#, Java)和AOT(C,C++)编译器,也有像解释型语言如JavaScript。

注意:文章中我所提到的"Python"均指使用C语言实现的CPython。

为什么要比其他语言慢到2-10x的速度?

这是相关原因:

  • 它的GIL(Global Interpreter Lock)
  • 因为是解释器型非编译型
  • 因为是动态语言

那这些原因中哪个占最大成分呢?

它的GIL

现代计算机大多数都具备多核,有时还有多处理器。为了充分利用这些处理能力,操作系统底层提供了一个叫做线程的东西,它(例如Chrome浏览器)可以系统内容创建多个线程进行指令处理。也就是说当一个进程是CPU密集型,那就可以通过多个核协同工作来提高应用的运行速度。

我本地Chrome浏览器当前会开启44个线程,在不同的操作系统例如POSIX(Mac OS和Linux)和Windows提供的线程API结构不一样。操作系统来负责线程的调度。

如果你之前并没有进行过多线程编程,你需要熟悉一个叫做锁的概念。不像单线程进行,你需要确保在内存改变一个变量时,多个线程不会同时进行操作。

CPython创建变量时,它会开辟内存,然后计算有多少引用该变量,这个概念叫做引用计数。如果引用技术为0时,它会将内存释放回给系统。这也就是创建临时变量,进行循环操作时并不会耗光内存

在多线程共享变量时,CPython时如何对引用计数上锁呢。这里就是“全局解释锁(global interpreter lock)”负责做的事情,不管你有多少的线程,解释器在同一个时间只能有一个线程进行操作。

那么它对Python应用有什么性能影响

如果应用是单解释器,单线程。在速度上没有任何影响。

如果你想在单解释器使用线程了实现并发操作,并且它们是IO密集型(例如网络IO或者硬盘IO),那你将会看到GIL类似如下竞争执行:

如你有一个web应用(例如Django)并且使用WSGI,每个请求将会分配到单独Python解释器,此时一个请求只有一把锁。因为Python解释器启动比较慢,一些WSGI会实现为"Daemon Mode"执行,

那其他Python运行时呢

PyPy实现的GIL通常要比CPython快3x倍。

Jython没有GIL,因为Jython的线程受益与JVM的内存管理机制。

JavaScript 是怎么实现的

首先,JavaScript使用的是标记清除算法实现的垃圾回收机制。而CPython需要GIL主要原因就是内存管理算法。

JavaScript没有GIL,但是因为它设计的就是单线程,所以它并不需要。JavaScript的event loop和Promise/Callback机制来进行异步编码实现并发。Python也有类似的asyncio的event-loop机制。

因为它是解释型语言

如果你在终端使用python myscript.py运行,CPython将会进行一系列的读取,词法分析,语法分析,编译,解释和执行代码。

一个非常重要点事,在编译过程中生成的.pyc文件,Python3是放置在__pycache__目录下,Python2是在文件相同目录下。该文件就是Python里面的字节码,在执行文件不会生成,只会在倒入的模块或者第三方模块生成。

所以,Python解释成字节码并且进行运行。与Java和C#.NET相比:

Java 编译成一个中间语言,然后JVM加载字节码,进行just in time编译成机器码。.NET CLI也是同样的方式,.NET common language runtime使用just in time编译成机器码

那么,Python为什么要比Java和C#测试性能差那么多,都是使用字节码,区别就在于JIT编译方式。

Just in time需要一个中间语言允许代码被拆为多个chunks(或者frames),AOT编译器设计用来确保CPU能够理解里面的内容。

JIT本身没有提高代码执行,但是因为它执行仍然是字节码。然后,JIT允许在运行中优化执行。一个好的JIT加应用程序执行很高的代码,将字节码直接优化为机器码,从而提高执行效率,这种技术成为 Hot Spot。

也就是说在程序一次次执行过程中,会变得越来越快。还有就是,Java和C#是强类型语言,所以优化器能够进一步优化。

PyPy也有JIT,设计比CPython执行更快。

那么CPython为什么不用JIT

JIT也有缺点,就是减慢了启动时间。CPython启动时间已经很慢了,PyPy更是比CPython慢2-3x倍。JVM启动速度是臭的不行。.NET CLR在系统启动的时候就先启动了。

如果你有一个Python进程要长时间运行,那么可以使用JIT的hot spots带来的益处。

然而,CPython设计为通用语言。所以,当你在实现命令行应用时,每次都要长时间等待JIT启动那是非常讨厌的事情。

CPython也做很多尝试,也尝试性使用Plugging方式加入JIT,但是项目现在是停滞的。

因为它是动态类型的

在静态类型语言中,你必须在申明时就定义它的类型,这些语言有C,C++,Java,C#和Go。

在一个动态类型元中,虽然也有类型的概念,但是一个变量的类型是动态的

a = 1
a = "foo"

在上面示例中,Python使用了相同的变量赋于了不同的str类型,它会释放第一次创建的内存。

静态语言并不是设计来让你编码头疼,而是为了适应CPU的操作方式。因为所有的操作都是二进制操作,你需要将所有的对象和类型转换为低级别的数据结构。

Python已经为了做了这件事情,你看不见也不需要关系。

不用申明类型并不是导致Python变慢的直接原因,这样设计让你几乎所有操作都是动态的。你可以在运行时替换对象的方法,你可以在运行时对底层系统调用进行monkey-patch操作,一切皆有可能。

这样的设计导致Python很难进行优化。

为了验证我的观点,我将在我的Ubuntu系统中使用syscall追踪工具Dtrace。CPython并不内建DTrace,所以你需要重新编译CPython,我将使用Python 3.7.0:

./configure --with-dtrace
make

现在代码中就可以使用Dtrace进行追踪了,你可以下载工具来对Python的函数调用,执行时间,CPU时间,syscalls等进行分析:

sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’

py_callflow追踪器打印了类似如下信息:

所以,Python动态类型是否让它变慢呢

  • 对比和转换类型代价很高,每次变量读取,类型检查时都很耗时。
  • 动态类型很难对语言进行优化。Python其他替代方案要快,是因为它们为达性能对灵活性做出了妥协。
  • 看看Cython,将C静态类和和Python优化类型可以在84x性能提高。

结论

Python主要慢的原因是因为它的动态和灵活性。它可以作为解决大多数问题的工具,也存在更优更快的可选方案。

可以在应用程序中利用async,理解性能工具,考虑使用多解释器等进行优化。

如果对于启动时间不是那么关心的话可以考虑使用JIT,例如PyPy。

对于性能要求比较苛刻的,你可以使用更多的静态类型变量,考虑使用Cython

为什么Python如此慢的更多相关文章

  1. Python中的多进程与多线程(一)

    一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...

  2. Python高手之路【六】python基础之字符串格式化

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...

  3. Python 小而美的函数

    python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况   any any(iterable) ...

  4. JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议

    软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...

  5. 可爱的豆子——使用Beans思想让Python代码更易维护

    title: 可爱的豆子--使用Beans思想让Python代码更易维护 toc: false comments: true date: 2016-06-19 21:43:33 tags: [Pyth ...

  6. 使用Python保存屏幕截图(不使用PIL)

    起因 在极客学院讲授<使用Python编写远程控制程序>的课程中,涉及到查看被控制电脑屏幕截图的功能. 如果使用PIL,这个需求只需要三行代码: from PIL import Image ...

  7. Python编码记录

    字节流和字符串 当使用Python定义一个字符串时,实际会存储一个字节串: "abc"--[97][98][99] python2.x默认会把所有的字符串当做ASCII码来对待,但 ...

  8. Apache执行Python脚本

    由于经常需要到服务器上执行些命令,有些命令懒得敲,就准备写点脚本直接浏览器调用就好了,比如这样: 因为线上有现成的Apache,就直接放它里面了,当然访问安全要设置,我似乎别的随笔里写了安全问题,这里 ...

  9. python开发编译器

    引言 最近刚刚用python写完了一个解析protobuf文件的简单编译器,深感ply实现词法分析和语法分析的简洁方便.乘着余热未过,头脑清醒,记下一点总结和心得,方便各位pythoner参考使用. ...

  10. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

随机推荐

  1. PHP之冒泡排序的优化

    冒泡排序是一个常见的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成. 对于冒泡排序 ...

  2. IO流 相关基础积累

    一个整型32位字节. 写入到流.? 为什么要按十六进制输出到控制台?

  3. 2018.5.31 nRF905 test

    1 试电机:自动控制测试流程(Labview程序,加载扫描仪,自动测试夹具,测试数据保存) 2 USB RF收发器: 含S/N码发送读取功能(S/N:) The specific use please ...

  4. Oracle忘记用户名密码

    一.oracle 11g登录服务开启 成功安装Oracle 11g后,共有7个服务,这七个服务的含义分别为:1. Oracle ORCL VSS Writer Service:Oracle卷映射拷贝写 ...

  5. windows 7下mingw+msys编译ffmpeg

      windows 7下mingw+msys编译ffmpeg   1-->下载安装MingW,mingw-get-inst-20120426.exe  http://sourceforge.ne ...

  6. bzoj 2969: 矩形粉刷 概率期望

    题目: 为了庆祝新的一年到来,小M决定要粉刷一个大木板.大木板实际上是一个W*H的方阵.小M得到了一个神奇的工具,这个工具只需要指定方阵中两个格子,就可以把这两格子为对角的,平行于木板边界的一个子矩形 ...

  7. JS性能之滚动条之外的其他部分

    问题: 如果一个页面宽高比较大,也就是页面需要滚动条来查看其他页面内容,这时候,在滚动条之外的其他部分,是依然处于运行状态. 比如那部分有视频播放,则那些视频虽然在当前窗口看不到,但它们还是会处于播放 ...

  8. easy_install下载地址及安装

    下载地址 https://pypi.python.org/pypi/setuptools 解压 tar -xzvf xx.tar.gz 安装 cd 解压目录 sudo python setup.py ...

  9. java基础知识(4)---设计模式

    设计模式:解决问题最行之有效的思想.是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. java中有23种设计模 ...

  10. Installshield build all installer in development computer

    Step: Copy all "SetupPrerequisites" from build server. please make sure below items: Insta ...