范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

如果Python4。0摆脱了GIL

  作者:orenwang,腾讯IEG应用开发工程师
  | 导语   GIL,即全局解释器锁,是阻碍 Python 多线程并发计算性能提升的最大原因,也是众多 Python 开发者的心头之痒,而 Sam Gross 大神的新项目 nogil 却在过去几个月的时间里硬生生地撬开了这把锁。
  There should be one- and preferably only one -obvious way to do it.
  - Zen of Python
  1992年的一天,Python 之父 Guido van Rossum 为 Python 引入了一种简单而又优美的机制:让程序运行无需再担心死锁,因为全局只有一把锁;功能实现更加简洁,无需再针对单个对象加锁和解锁,因为全局只有一把锁;甚至大幅提高了计算速度,这来源于程序本身的低 Overhead 和独特的 Garbage Collection 机制,因为全局只有一把锁。 这把锁就是 GIL,即全局解释器锁。
  我们快进到1998年,硬件行业在这一年发生了一个重要的变化:多核处理器被研制出来了。大家很快意识到 GIL 在单线程领域的强大,却成为了多核计算时代的绊脚石。因此在 1999年 Python 1.4 版本期间出现了一个叫 "free-threading" 的包,大刀阔斧地移除了 GIL,然而单线程计算速度却慢了4到7倍。
  而我所知道的最近一次移除 GIL 的尝试是  2016 年 Larry Hastings 大神提出的 Gilectomy  项目,其移除了 GIL 之后单线程计算仅慢了 30%,然而该项目的主要问题在于 核越多,计算越慢(7核下慢19倍)。
  由此可见,Guido 爸爸写的这段代码,尽管每天被全球开发者吐槽,但真搞起来,想比人家做得更好并不容易。 GIL 的问题
  举一个简单的例子:给你一张纸,上面有100个格子,让你从数字1写到100,一个格子一个数字,你觉得需要多久?我闲来试了一下,花了我82秒(好像真的很闲)。那好,现在假设你们有五个人,每个人只要写其中20个数字即可,你觉得需要多久?简单地看,82秒除以5,五个人大约16秒即可完成。但如果你们五个人只有一支笔呢?算上你们互相传递笔的时间,恐怕82秒也不够了。
  纸上画数字的例子(动画内可以想象成100个人+100支笔同时写字)
  上面的例子里,这支笔,在 Python 的世界里就是 GIL:无论你们有多少人,只能有一个人拿着笔,其他人只能等着这个人把笔放下,才能开始写字;无论你的 Python 程序起了多少个线程,真正吃 CPU 干活的只有一个线程。之所以这里强调了一下吃 CPU,是因为  GIL 的设计仅对 CPU-bound 的程序有限制 ,而在处理 IO-bound 计算时,是不需要 GIL 这支笔的,大家可以同时干 I/O 的活。
  这时很多人会好奇,为什么不直接使用 multiprocessing 库进行多进程计算呢?当然可以,但是 multiprcessing 的实现实际上是"fork"了个新的进程,性能牺牲了不说,死锁的问题也将会暴露出来,更不用说如 CUDA 等很多第三方库是不支持"fork"的。
  再说一点,实际上,大部分人吐槽 GIL 的点,并非是 Python 程序本身并发效率的问题,而是大多数对于计算速度有要求的库都是 Python 调用 C/C++,而 GIL 限制了你在调用 C/C++ 时也只能真正同时运行一个线程。也 难怪 Sam 大神想要移除掉 GIL,他作为 PyTorch 的核心作者,自称因性能问题曾大面积地把 Python 代码完全重写成了 C/C++    ,也因此很多人说 PyTorch 跟 Python 关系已经不大了。相比之下,Swift 团队曾写过一篇 "Why Swift for Tensorflow" 点出了相比 Python 的 GIL 性能瓶颈, Swift 在训练 AI 的性能方面具备优势;而Python 的第一竞品 Julia 开发者和爱好者们更是揪着 GIL 这一点屡屡不放手,"怂恿"大家转向使用 Julia 做数据科学工作。
  综上,现今 GIL 怕是过大于功了。 一些不那么基础的基础知识
  接下来,本文会讲解一些技术细节,虽然尽可能写得通俗易懂(从而不暴露自己其实也不懂),但如果不够熟悉 Python 的话可能还是会觉得有些不知所云... CPython
  简单来讲,CPython 就是我们用的 Python。只是为了更容易地与"Python这门语言"进行区分,我们一般把运行 Python 解释器的这个引擎叫做 CPython(我一开始就把 CPython 跟 Cython 项目搞混了,但其实 Cython 和我们本文说的就不是一回事了,它只是个把 Python 变成 C 的工具)。那除了用 C 写的 CPython,其实还有用 Java 和 C# 分别写的 Jython 和 IronPython。值得注意的是,后两者并没有 GIL,因此 GIL 并不是 Python 这个语言的特性/问题,而是 CPython 实现中包含的,因此下文与 GIL 相关的都会用 CPython 这个名字进行阐述。 Reference Counting
  当你有了变量的时候,CPython 就已经开始计数(counting)了,而当这个变量出现在任一列表(list)或者字典(dict)或者函数(function)内的时候,计数都会增加。当使用变量的函数执行完毕,或这个变量被 pop 出了某一个列表的时候,CPython就会把这个变量的计数对应减少。而当某个变量计数为零的时候,这个变量所在的内存就可以被释放掉了,可以看CPython 源码这里(https://github.com/python/cpython/blob/main/Include/object.h#L520)就是这么写的。
  具体计数方式也可见下面的代码例子,我个人觉得看代码更容易理解:
  而这个 Reference Counting 有意思的地方就在于: 程序释放变量对应的内存空间无需等待GC工作时再进行操作了! 因为只要计数为零,就满足了条件。那为什么 CPython 还是需要 GC 呢?我这里一下子也没想通,查阅了一下资料发现原因其实很简单,因为如果有几个变量没其他地方用到了,但是它们互相之间是有 reference 的,那这个时候仅靠 Reference Counting 去释放内存自然就会发生内存泄漏。 Atomicity
  继续用上面"纸上写数字"的例子,当你在写数字"17"时,你要先写个"1",再写个"7"吧。可能在你写"7"之前,会有人把你的笔抢走,这个OK。但你起码不能在写"1"写到一半的时候,允许别人打断你。换句话说,你要 有一个最"原子"的行为,这个行为无法进一步再拆分,你就Atomic了 。
  这个概念,有一些数据库知识基础的话,应该不需要解释,跟大部分数据库所保证的 Atomic 是不同场景下的同一个意思。 Concurrent Collection Protections
  列表(list)或者字典(dict)这类对象,我们都可以称之为  Collection ,这些对象往往在类似 Python 这种语言内都有各自独特的内存结构,有的结构倾向于计算速度,而有的结果是出于内存占用考虑进行了优化。但无论哪种结构,在出现并发的的情况下,这些  Collection  都存在线程安全问题,因此处理时底层往往有一定的并发锁逻辑进行保护,这个相信不难理解。 改写历史的 nogil 项目的技术细节
  Sam 大神的新项目  nogil  之所以获得了如此大的关注度,也首次引起 CPython 核心团队好评的原因不仅是它成功移除掉了 GIL (而非类似 per-interpreter GIL 那种半移不移的设计),同时也克服了绝大多数前人未能解决的问题,而且最终性能分数惊人。通读了 Sam 的原 paper 后,我又翻阅了几篇大神们对该工作的讨论和文章,感觉这个项目成功的核心倒不是设计上有多么巧妙(当然人家非常非常非常巧妙),难得的是  nogil  把 Python 目前版本里几个 "浪费" 掉的地方拎出来逐一进行了深度优化,只不过"深"到已经把 Python 的内存分配器  PyMalloc  都直接换掉了。正像 Larry Hastings 所说,难的不是移除掉 GIL,难的是移除掉了 GIL 还能保证以前的东西就像没移除掉一样好用... 那 Sam 是怎么做到呢?这里讨论下我觉得比较有意思的几点: Biased Reference Counting
  这其实是2018年ACM上的一篇论文  Biased Reference Counting  提出的一种全新 Reference Counting 理论:并发多个线程同时进行 Reference Counting 操作时,我们往往需要把每一次操作  Atomic  化,这样才能保证各个线程之间得到的 count 值保持一致;但我们忽略了一个因素,如果一个对象经常会被某一个线程操作,而被其他线程操作的频次很少,那我们是不是可以给这一个类似 "owner" 的线程一些特殊的优化,即便让其他的线程慢一点也影响不大?
  而事实上,绝大多数对象都是面临这样一种情况。所以,这里我们就 "Biased" 了,让 "owner" 线程的 Reference Counting 操作速度达到极致,而不用保证 Atomic ,只需要让其他所有的线程 Atomic 即可(好吧,这里我也不是很懂,为什么 Non-Atomic 就一定比 Atomic 要快,但我知道为了做到 Atomic 显然要做某些牺牲,等有时间我再具体看看为啥,然后补充到这里)。这一点非常关键,是整个  nogil  项目对性能贡献最大的一点,我画了个动画帮助理解:
  Immortalization
  上面的 Biased Reference Counting 好用的前提是"大多数变量只有一个线程会经常使用",但对于那些 0、1、True、False、 None之类的变量呢?这些变量可是几乎每一个线程都要频繁使用的。为了提高这类变量的操作速度, Sam 很巧妙地把这些变量 Immortalize(永久化)了,使得这类变量的引用不再需要做计数! 我看到了这里,就有种强烈的"md我怎么没想到"的感觉。
  不过实现 Immortalization 也不是没有牺牲的:计数值的 LSB(最低有效位,Least Significant Bit)不能再用了,因为 LSB 被用来代表这个变量是不是可以永久化掉了。这里会结合下面的 Deferred Reference Counting 再多讨论一些。 Deferred Reference Counting
  继续揪着 Reference Counting 不放:那些既不能被永久化掉的同时又需要频繁使用的对象怎么办(怎么有点谐音梗...)?这个第二低有效位也被拿来征用了,被用来表示某个对象是否需要"Defer"它的引用计数。这个"Defer"的意思我个人感觉有一点误导,因为它其实 并非"延后",根本就是不再计数了 ,把所有 释放 相关的工作都交给 GC (Garbage Collector)了,毕竟很多引用的 top-level functions 或者 modules 本来就是只能被 GC 给释放掉。
  这里的具体实现我也不是很懂,但知道大概是因为局部变量一般是在内存的 Stack 上,Deferred Reference Counting 是完全不用管 Stack 上的计数变化,但如果一个对象的引用是被放在 Heap 上的,这个时候计数其实是照常的,只不过不会因为 Heap 上的计数为 0 而直接释放掉它,毕竟这个时候有可能有 Stack 内存还在引用它。
  Immortalization 和 Deferred Reference Counting 加起来一下就用掉了两个最低位,也就是说以后每次调用 Py_INCREF 和 Py_DECREF,Reference Count每次变化就是 4 了,感觉怪怪的。不过按 Sam 的原话,这里其实变化是 1 还是 4 并不重要,毕竟我们大部分情况下只关心这个计数是不是零就够了。这么说,也确实有些道理。 Mimalloc
  Python 的内存分配器  PyMalloc  被换成  mimalloc  了。看 mimalloc 文档看到第二段就感觉好厉害:
  mimalloc is a drop-in replacement for malloc and can be used in other programs without code changes
  这哪里是换掉  PyMalloc, 这原来是可以直接换掉  malloc  了 …
  具体实现细节我就没有看了,因为我知道我肯定看不懂。但是这里使用它的原因就很明显了:因为  PyMalloc  有 GIL 的保护,所以不需要也做不到 thread-safe,而  mimalloc  可以让 Python 做到 thread-safe 同时性能大幅提升。 Collection Read-only Access
  写到这里,终于写到了码农们熟悉的  list  和  dict  对象了。
  当我们引用或一个  list  或  dict  对象,发生的过程大致可以简单地分成三个步骤: 加载这个对象的地址 修改对象的 Reference Count 返回这个对象的地址
  这一切在 GIL 的保护下没什么问题。然鹅现在我们没有 GIL 了,这里会出现一个问题:当有一个线程执行写操作时,在步骤 2 把这个对象释放掉了(Reference Count 减少到 0),而这个时候又有一个线程已经完成了步骤 1,开始直接步骤 2 时,就崩溃了,因为这个对象已经被释放掉了。
  Sam 的设计是,既然我们没有 GIL 这把全局锁了,我们就要给单个对象加局部锁,不过我们只对写操作加锁。具体实现简单来说就是增加了  Reference Counting 版本控制 和更多的 检查判断并重试机制 ,比如在执行上述的步骤 2 时首先检查对象是否 Reference Count 已经为 0 了,如果是的话,从步骤 1 开始重试(重试之后我理解就可以读到一个新的地址或是可以识别出是空地址从而保证安全性)。
  目前对于  list  和  dict  的重新设计,主要是对单线程处理速度进行了优化,对于多线程处理只能保证安全而速度上有一定程度的牺牲。也许以后会出现一些特殊的  collection  类型,以应对那种多线程频繁调用的情况。 Python 4.0 是否真的会移除GIL?
  我个人感觉,出现一个没有GIL版本的 Python 4.0 的可能性是比较大的,毕竟  CPython 核心团队其实已经在着手将 Sam 大神的  nogil  项目合入 Python 3.11 了 ,而且该项目的性能分数已经达到甚至部分超过了 Guido 爸爸之前对于拿掉 GIL 的基本条件,这一次没有"借口"可以拒绝了。当然 Python 3.11 多半不会是一个无 GIL 版本, nogil  项目无论多强大它也还只是个实验项目,其仍存在诸多大小问题,以及很多仍待讨论的架构决策,都不是一个小版本就能够解决掉的。
  至于 Python 4.0,它自己本身就是个未知数。核心团队自己已经重申了多次他们想尽量延后 Python 4.0 的时间,因为 Python 2.0 到 3.0 大家已经很伤了,这么快又搞一波怕大家心里承受不了…Guido 爸爸很久就曾发推解释过一次:
  这里我想吐槽一下,从 Python 3.5 开始,每个版本就已经很伤了好么还不如赶紧上  nogil  也算是个痛并快乐着。
  无论结果如何,我作为一个被 Python 领进门的、被 Python 各种骚操作种草的、到现在不管后端服务还是客户端脚本还是各种AI"小研究"都首选 Python的忠实粉丝,衷心祝愿 Python 未来...越来越妖!

节后保养,4项即可中秋节一过,带着兴奋和疲惫的车主又恢复到朝九晚五的生活,随行相伴的爱车也一样。但是,在经过假期加班之后,爱车或多或少都有些磨损。所以,车主应尽早带爱车去做一次体检。车外清洗,车内除白海军我的预言正在落地,基辅安全协议草案如同俄战败条约当地时间9月13日,乌克兰总统办公室发布了关于乌克兰安全保障的一个协议草案。该草案设想,应当签署一份基辅安全条约,由美国英国加拿大波兰意大利德国法国澳大利亚土耳其波罗的海国家等作为名宿克洛普应给努涅斯更多机会,不认为菲尔米诺和若塔更出色直播吧9月15日讯利物浦名宿史蒂夫尼科尔接受了媒体的采访,谈及没能获得太多出场机会的努涅斯,尼科尔发表了他的观点。尼科尔表示当我看利物浦的比赛,第一件事就是关注下努内斯有没有受伤,北青U19国青轻取马尔代夫胜不足喜出线形势依旧严峻北京时间今日凌晨进行的U20亚洲杯预选赛A组第3轮角逐中,U19国青以5比0战胜马尔代夫U19队从而收获两连胜。北京青年报发文点评国青表现。文章中认为,U19国足的综合实力远在对手逻辑对决,破晓加冕!WPL狼人杀英雄联赛淘汰赛第二轮今日开战逻辑对决,破晓加冕!2022狼人杀英雄联赛淘汰赛第二轮赛程今晚正式启动,36支门派齐聚,决出5支门派进入WPL总决赛!破晓之际,究竟哪些门派能够成功脱颖而出,成为全国十二强呢?让我欧冠,尤文vs本菲卡,尤文主场能否战胜火爆的本菲卡?尤文因为独特的温柔球风被大家尊称老妇人,这两年因为我罗的离开,在意甲也不再独领风骚,连续霸榜9年的尤文图斯虽然不再风骚,但是江湖依然有他的传说,关注度依然非常火热,上上个赛季获得了第5轮法甲联赛朗斯52洛里昂全场集锦收录登场于北京时间2022年9月1号展开的法甲联赛火并战役,朗斯主队前往法甲联赛与洛里昂克队展开紧张的较劲,最终这场刺激较量以52比分落幕,并且由朗斯击败洛里昂来成为法甲最终胜者。朗斯欧冠超级大黑马诞生!豪取2连胜1球未丢,力压3大劲旅登顶今天凌晨,欧冠小组赛B组的一场比赛,爆出了大冷门,布鲁日客场40大胜波尔图,豪取开局2连胜,积6分登顶小组积分榜。能够在这样一个强敌环绕的小组中登顶,对于布鲁日来说无疑是个不小的成刘洋绝境破门救主!泰山队险平残阵三镇,终结对手连胜成唯一收获北京时间9月14日,中超迎来一场焦点对决,泰山队主场迎战武汉三镇,三镇此役只有一名外援首发,上半场高准翼兜射破门,半场泰山队01落后。下半场刘洋扳平比分,贾德松替补出场了20分钟后哈兰德OR恩德里克才是伯纳乌未来进攻大旗!皇马赛季消息汇总09141。马卡皇马担心对维尼修斯使用恶意犯规的球队会越来越多个人观点对马洛卡的比赛中明显看到一些熊皇表现出来的情绪变化,为了过人而过人,很明显!年轻人的情绪变化很正常,比赛经验积累多了,欧冠曼城VS多特蒙德,哈兰德擒旧主,蓝月亮状态势不可挡曼城新赛季英超联赛6轮取得4胜2平的战绩,积14分排名英超第2位。球队共计打入20球仅丢6球,随着锋霸哈兰德的加盟,球队进攻端变得更加犀利,成为英超目前进球数最多的球队,哈兰德也成
湘西文学改版座谈会在龙山里耶镇举行红网时刻新闻1月13日讯(通讯员尚明文)1月12日晚,湘西州作家协会湘西文学编辑部在龙山县里耶镇召开座谈会。州文联副主席州作协主席黄挺主持会议。座谈会总结了湘西文学创刊一年来的工作时间定了,油价又要变油价消息近5个工作日油价出现大幅下跌,每升跌破2毛!本轮油价调整将在春节前(17日24时)开启,当前统计日期已超过半数,较前一个工作日再降了45元吨,下调幅度为245元吨,折算后超14年挣了1250亿美元!苹果遮羞布被撕碎,外媒吐槽吃相难看都知道,在过去这些年里,苹果AppStore统一收取30的佣金,这也被外界称之为苹果税,而因涉嫌垄断,苹果税也遭到了欧洲的重罚,苹果收到关于AppStore的罚单相当多,但无论罚金乡爱15才播6集,就有一大批人想弃剧了,理由竟让人无力反驳每年过年,等不到赵本山的乡村爱情系列作品,总让人觉得缺点什么,但终于等到了又很有可能因为品质差强人意,让不少观众食之无味,弃之可惜。乡村爱情15给人的感受就是如此,这部剧作才播6集林业专家做客直播间,带你揭秘林业有害生物防治这些事!林业有害生物防治工作事关森林资源安全生态环境改善可持续发展目标的实现。2022年1月1日贵州省林业有害生物防治条例的颁布实施,对用法治手段打击违法违规行为,控制防治林业有害生物疫情奥密克戎XBB变异株出现在我国,你的假期出行还安全吗该来的还是来了,新冠肺炎奥密克戎XBB变异株在我国被检测出来,这个假期你的出行还安全吗?在国务院联防联控机制就优化中外人员往来管理措施有关情况举行发布会。有记者提问,据报道,国内已预防心脏病,做好这五件事,一辈子护好心脏随着科技的发展,心脏病已经成为当今社会最为常见的疾病之一,它不仅可能会对我们的身体健康造成严重的危害,而且它也让我们离死神又近了一步。为了保护我们的心脏健康,我们应该做好以下五件事1月31日是清欠最后大限,广州城队前途未卜记者王伟报道近日,申花股改尘埃落定,久事集团入主,这支老牌球队的历史欠薪问题解决顺利。那么,同样有历史欠薪问题的广州城队,近况如何?据了解,完成中超保级的广州城俱乐部,股改至今尚未iOS16。3beta2正式推送,续航提升太顶了,信号大幅优化,推荐果粉们期待已久的iOS16。3beta2来了!这是距离上次beta1版本时隔27天发布的第二个预览版,这次更新后内部版本号为20D5035i,很多用户更新后都表示,可以养老了!那么WEB3。0常用术语合集Web3在很大程度上仍然是理论上的,并且建设曲线相当曲折。目前,任何想要加入的人都必须对区块链和加密货币技术进行自我学习。最近收到很多刚进入Web3。0的小伙伴私信,发现在web3Apple新系统来了,iOS也要让路果粉外媒称,在最新PowerOn通讯稿中表示,Apple开发团队的重心现在偏移到xrOS系统。另外,导致内部代号为Dawn的iOS17系统更新规模要比预期的小很多。而xrOS是专门