Skip to content
AD
AD

碎碎念

本页不是教程

是碎碎念……

性能问题

一句话,如果你决定在你的项目中使用一种语言,你应当已经确定使用这门语言能给你的项目带来的好处,并接受后果。

如果选择了Python,你自然失去了如C/C++那样高效的性能,而收获了高拓展性、低维护成本的优势。

很显然,Python的性能固然是问题,但罪不至成为攻击Python这门语言的原因。既然都选择了Python,还想要追求性能,就像太怕疼所以全点了防御,然后问为什么打人像刮痧。

若是真既要拓展又要性能,保留项目框架是Python,把很耗时的地方写成C,或者用Go、Java、以及任何性能更好的语言写给Python调用呗。

如果想要用Python去打比赛,然后来质问Python怎么这么慢,那我也不好说什么了。

这可不是说Python程序完全不需要在意性能,程序的性能当然是越快越好,在Python层面进行优化自然也是可接受的。只是不要看见Python就提“你竟然用了Python”,或者看到Python的性能优化就说“你都用Python了”,那样会显得提的人水平不高。

说多了也挺没意思的。

GIL(Global Interpreter Lock 全局解释器锁)

许多人都会告诉你,Python中的多线程是假的,同时只有一个线程在运行,这都是因为Python的GIL。他们可能会把GIL称作全局线程锁,说这个东西影响了Python多线程的效率,似乎也是Python性能问题的一大罪证。

有点道理,但不多。

今天的多线程似乎主要目的变成了充分利用多核CPU的多个核心,所以既然同时只有一个线程在运行,那这多线程肯定是假的了。

他们可能并不知道,多线程是诞生在一个CPU还没有多核化的时代的。自始至终,无论是多线程、多进程、异步还是协程,最主要的目的都是为了实现并发(或者说并行)。

进程没有这方便的考虑,因为每个进程独享资源;其他的并发方式天生面临竞争冒险的问题,他们使用相同的内存,操作相同的变量,如果多个并发同时操作一个变量导致了毫无厘头的意外问题,这锅算谁的?

异步和协程又由于其手动移交控制权的方式天生规避了这个问题,线程就只能加锁:在操作变量前加锁、操作结束解锁,其他线程遇到锁就硬等到解锁再执行。

我们知道Python的底层是C,CPython通过引用计数的方式来自动释放不再使用的内存,即垃圾回收。引用计数,计的是Python中对象的引用数,而在解释型语言Python中我们知道,万物皆对象。

那么如果多个线程在操作同一对象(更改引用数)时发生了线程控制权的转移,引用计数自然会出问题,导致对象被过早回收(产生Python层面的Error)或者对象迟迟无法释放(C层面的内存泄漏),这是不可接受的代价。

于是Python的设计者们设计了一个全局的解释器锁GIL(不是全局线程锁),确保即使在多线程中解释器也只能同时操作一个对象。在接下来的二十多年中,GIL助力Python越走越远,也迎来了越来越大的非议。

注意GIL不是一个全局的线程锁。你的Python代码中多线程对全局变量的操作依然需要手动加锁,GIL保证你的代码在底层是安全的,但代码出现bug不在GIL的管控范围内。

聪明的孩子会问其他语言是如何解决这个问题的?答案是无法借鉴,因为Python是一门解释型语言,在新时代的C++、Java身上使用的方案很难照搬。世界上只有Python一门解释型语言吗?并非如此,只是解释型语言Ruby同样存在GIL,解释型语言PHP自身对多线程支持有限,解释型语言Javascript就不谈了……

所以说,Python的多线程存在GIL,不是Python的错,而是选择Python时你需要接受的代价。你已经可以毫不担心内存泄漏地调来自五湖四海的库了,还想追求压榨GIL的这一点性能,未免太不厚道了。

以及,在实验性去除GIL的Python3.13版本中,无GIL版的Python的单线程运行效率也受到了影响。

你以为是更快了吗,其实是更慢了。GIL绝对不是一拍脑袋就做出来的决定,至少这几十年间,它为Python做出的这些贡献,不应该因为一些所谓Python调包侠的三言两语就被彻底污名化。

贡献者

页面历史