日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

作者:orenwang,騰訊IEG應(yīng)用開(kāi)發(fā)工程師

| 導(dǎo)語(yǔ) GIL,即全局解釋器鎖,是阻礙 Python/ target=_blank class=infotextkey>Python 多線(xiàn)程并發(fā)計(jì)算性能提升的最大原因,也是眾多 Python 開(kāi)發(fā)者的心頭之癢,而 Sam Gross 大神的新項(xiàng)目 nogil 卻在過(guò)去幾個(gè)月的時(shí)間里硬生生地撬開(kāi)了這把鎖。

There should be one- and preferably only one -obvious way to do it.

- Zen of Python

1992年的一天,Python 之父 Guido van Rossum 為 Python 引入了一種簡(jiǎn)單而又優(yōu)美的機(jī)制:讓程序運(yùn)行無(wú)需再擔(dān)心死鎖,因?yàn)槿种挥幸话焰i;功能實(shí)現(xiàn)更加簡(jiǎn)潔,無(wú)需再針對(duì)單個(gè)對(duì)象加鎖和解鎖,因?yàn)槿种挥幸话焰i;甚至大幅提高了計(jì)算速度,這來(lái)源于程序本身的低 Overhead 和獨(dú)特的 Garbage Collection 機(jī)制,因?yàn)槿种挥幸话焰i。這把鎖就是 GIL,即全局解釋器鎖。

我們快進(jìn)到1998年,硬件行業(yè)在這一年發(fā)生了一個(gè)重要的變化:多核處理器被研制出來(lái)了。大家很快意識(shí)到 GIL 在單線(xiàn)程領(lǐng)域的強(qiáng)大,卻成為了多核計(jì)算時(shí)代的絆腳石。因此在1999年 Python 1.4 版本期間出現(xiàn)了一個(gè)叫 "free-threading" 的包,大刀闊斧地移除了 GIL,然而單線(xiàn)程計(jì)算速度卻慢了4到7倍。

而我所知道的最近一次移除 GIL 的嘗試是 2016 年 Larry Hastings 大神提出的 Gilectomy 項(xiàng)目,其移除了 GIL 之后單線(xiàn)程計(jì)算僅慢了 30%,然而該項(xiàng)目的主要問(wèn)題在于核越多,計(jì)算越慢(7核下慢19倍)。

由此可見(jiàn),Guido 爸爸寫(xiě)的這段代碼,盡管每天被全球開(kāi)發(fā)者吐槽,但真搞起來(lái),想比人家做得更好并不容易。

GIL 的問(wèn)題

舉一個(gè)簡(jiǎn)單的例子:給你一張紙,上面有100個(gè)格子,讓你從數(shù)字1寫(xiě)到100,一個(gè)格子一個(gè)數(shù)字,你覺(jué)得需要多久?我閑來(lái)試了一下,花了我82秒(好像真的很閑)。那好,現(xiàn)在假設(shè)你們有五個(gè)人,每個(gè)人只要寫(xiě)其中20個(gè)數(shù)字即可,你覺(jué)得需要多久?簡(jiǎn)單地看,82秒除以5,五個(gè)人大約16秒即可完成。但如果你們五個(gè)人只有一支筆呢?算上你們互相傳遞筆的時(shí)間,恐怕82秒也不夠了。


 

紙上畫(huà)數(shù)字的例子(動(dòng)畫(huà)內(nèi)可以想象成100個(gè)人+100支筆同時(shí)寫(xiě)字)

上面的例子里,這支筆,在 Python 的世界里就是 GIL:無(wú)論你們有多少人,只能有一個(gè)人拿著筆,其他人只能等著這個(gè)人把筆放下,才能開(kāi)始寫(xiě)字;無(wú)論你的 Python 程序起了多少個(gè)線(xiàn)程,真正吃 CPU 干活的只有一個(gè)線(xiàn)程。之所以這里強(qiáng)調(diào)了一下吃 CPU,是因?yàn)?nbsp;GIL 的設(shè)計(jì)僅對(duì) CPU-bound 的程序有限制,而在處理 IO-bound 計(jì)算時(shí),是不需要 GIL 這支筆的,大家可以同時(shí)干 I/O 的活。

這時(shí)很多人會(huì)好奇,為什么不直接使用 multiprocessing 庫(kù)進(jìn)行多進(jìn)程計(jì)算呢?當(dāng)然可以,但是 multiprcessing 的實(shí)現(xiàn)實(shí)際上是"fork"了個(gè)新的進(jìn)程,性能犧牲了不說(shuō),死鎖的問(wèn)題也將會(huì)暴露出來(lái),更不用說(shuō)如 CUDA 等很多第三方庫(kù)是不支持“fork”的。

再說(shuō)一點(diǎn),實(shí)際上,大部分人吐槽 GIL 的點(diǎn),并非是 Python 程序本身并發(fā)效率的問(wèn)題,而是大多數(shù)對(duì)于計(jì)算速度有要求的庫(kù)都是 Python 調(diào)用 C/C++,而 GIL 限制了你在調(diào)用 C/C++ 時(shí)也只能真正同時(shí)運(yùn)行一個(gè)線(xiàn)程。也難怪 Sam 大神想要移除掉 GIL,他作為 PyTorch 的核心作者,自稱(chēng)因性能問(wèn)題曾大面積地把 Python 代碼完全重寫(xiě)成了 C/C++ ,也因此很多人說(shuō) PyTorch 跟 Python 關(guān)系已經(jīng)不大了。相比之下,Swift 團(tuán)隊(duì)曾寫(xiě)過(guò)一篇 “Why Swift for Tensorflow” 點(diǎn)出了相比 Python 的 GIL 性能瓶頸, Swift 在訓(xùn)練 AI 的性能方面具備優(yōu)勢(shì);而Python 的第一競(jìng)品 Julia 開(kāi)發(fā)者和愛(ài)好者們更是揪著 GIL 這一點(diǎn)屢屢不放手,“慫恿”大家轉(zhuǎn)向使用 Julia 做數(shù)據(jù)科學(xué)工作。

綜上,現(xiàn)今 GIL 怕是過(guò)大于功了。

一些不那么基礎(chǔ)的基礎(chǔ)知識(shí)

接下來(lái),本文會(huì)講解一些技術(shù)細(xì)節(jié),雖然盡可能寫(xiě)得通俗易懂(從而不暴露自己其實(shí)也不懂),但如果不夠熟悉 Python 的話(huà)可能還是會(huì)覺(jué)得有些不知所云...

 

  • CPython

 

簡(jiǎn)單來(lái)講,CPython 就是我們用的 Python。只是為了更容易地與“Python這門(mén)語(yǔ)言”進(jìn)行區(qū)分,我們一般把運(yùn)行 Python 解釋器的這個(gè)引擎叫做 CPython(我一開(kāi)始就把 CPython 跟 Cython 項(xiàng)目搞混了,但其實(shí) Cython 和我們本文說(shuō)的就不是一回事了,它只是個(gè)把 Python 變成 C 的工具)。那除了用 C 寫(xiě)的 CPython,其實(shí)還有用 JAVA 和 C# 分別寫(xiě)的 Jython 和 IronPython。值得注意的是,后兩者并沒(méi)有 GIL,因此 GIL 并不是 Python 這個(gè)語(yǔ)言的特性/問(wèn)題,而是 CPython 實(shí)現(xiàn)中包含的,因此下文與 GIL 相關(guān)的都會(huì)用 CPython 這個(gè)名字進(jìn)行闡述。

 

  • Reference Counting

 

當(dāng)你有了變量的時(shí)候,CPython 就已經(jīng)開(kāi)始計(jì)數(shù)(counting)了,而當(dāng)這個(gè)變量出現(xiàn)在任一列表(list)或者字典(dict)或者函數(shù)(function)內(nèi)的時(shí)候,計(jì)數(shù)都會(huì)增加。當(dāng)使用變量的函數(shù)執(zhí)行完畢,或這個(gè)變量被 pop 出了某一個(gè)列表的時(shí)候,CPython就會(huì)把這個(gè)變量的計(jì)數(shù)對(duì)應(yīng)減少。而當(dāng)某個(gè)變量計(jì)數(shù)為零的時(shí)候,這個(gè)變量所在的內(nèi)存就可以被釋放掉了,可以看CPython 源碼這里(https://github.com/python/cpython/blob/main/Include/object.h#L520)就是這么寫(xiě)的。

具體計(jì)數(shù)方式也可見(jiàn)下面的代碼例子,我個(gè)人覺(jué)得看代碼更容易理解:


 

而這個(gè) Reference Counting 有意思的地方就在于:程序釋放變量對(duì)應(yīng)的內(nèi)存空間無(wú)需等待GC工作時(shí)再進(jìn)行操作了!因?yàn)橹灰?jì)數(shù)為零,就滿(mǎn)足了條件。那為什么 CPython 還是需要 GC 呢?我這里一下子也沒(méi)想通,查閱了一下資料發(fā)現(xiàn)原因其實(shí)很簡(jiǎn)單,因?yàn)槿绻袔讉€(gè)變量沒(méi)其他地方用到了,但是它們互相之間是有 reference 的,那這個(gè)時(shí)候僅靠 Reference Counting 去釋放內(nèi)存自然就會(huì)發(fā)生內(nèi)存泄漏。

 

  • Atomicity

 

繼續(xù)用上面“紙上寫(xiě)數(shù)字”的例子,當(dāng)你在寫(xiě)數(shù)字“17”時(shí),你要先寫(xiě)個(gè)“1”,再寫(xiě)個(gè)“7”吧??赡茉谀銓?xiě)“7”之前,會(huì)有人把你的筆搶走,這個(gè)OK。但你起碼不能在寫(xiě)“1”寫(xiě)到一半的時(shí)候,允許別人打斷你。換句話(huà)說(shuō),你要有一個(gè)最“原子”的行為,這個(gè)行為無(wú)法進(jìn)一步再拆分,你就Atomic了。

這個(gè)概念,有一些數(shù)據(jù)庫(kù)知識(shí)基礎(chǔ)的話(huà),應(yīng)該不需要解釋?zhuān)蟛糠謹(jǐn)?shù)據(jù)庫(kù)所保證的 Atomic 是不同場(chǎng)景下的同一個(gè)意思。

 

  • Concurrent Collection Protections

 

列表(list)或者字典(dict)這類(lèi)對(duì)象,我們都可以稱(chēng)之為 Collection,這些對(duì)象往往在類(lèi)似 Python 這種語(yǔ)言?xún)?nèi)都有各自獨(dú)特的內(nèi)存結(jié)構(gòu),有的結(jié)構(gòu)傾向于計(jì)算速度,而有的結(jié)果是出于內(nèi)存占用考慮進(jìn)行了優(yōu)化。但無(wú)論哪種結(jié)構(gòu),在出現(xiàn)并發(fā)的的情況下,這些 Collection 都存在線(xiàn)程安全問(wèn)題,因此處理時(shí)底層往往有一定的并發(fā)鎖邏輯進(jìn)行保護(hù),這個(gè)相信不難理解。

改寫(xiě)歷史的 nogil 項(xiàng)目的技術(shù)細(xì)節(jié)

Sam 大神的新項(xiàng)目 nogil 之所以獲得了如此大的關(guān)注度,也首次引起 CPython 核心團(tuán)隊(duì)好評(píng)的原因不僅是它成功移除掉了 GIL (而非類(lèi)似 per-interpreter GIL 那種半移不移的設(shè)計(jì)),同時(shí)也克服了絕大多數(shù)前人未能解決的問(wèn)題,而且最終性能分?jǐn)?shù)驚人。通讀了 Sam 的原 paper 后,我又翻閱了幾篇大神們對(duì)該工作的討論和文章,感覺(jué)這個(gè)項(xiàng)目成功的核心倒不是設(shè)計(jì)上有多么巧妙(當(dāng)然人家非常非常非常巧妙),難得的是 nogil 把 Python 目前版本里幾個(gè) “浪費(fèi)” 掉的地方拎出來(lái)逐一進(jìn)行了深度優(yōu)化,只不過(guò)“深”到已經(jīng)把 Python 的內(nèi)存分配器 PyMalloc 都直接換掉了。正像 Larry Hastings 所說(shuō),難的不是移除掉 GIL,難的是移除掉了 GIL 還能保證以前的東西就像沒(méi)移除掉一樣好用... 那 Sam 是怎么做到呢?這里討論下我覺(jué)得比較有意思的幾點(diǎn):

Biased Reference Counting

這其實(shí)是2018年ACM上的一篇論文 Biased Reference Counting 提出的一種全新 Reference Counting 理論:并發(fā)多個(gè)線(xiàn)程同時(shí)進(jìn)行 Reference Counting 操作時(shí),我們往往需要把每一次操作 Atomic 化,這樣才能保證各個(gè)線(xiàn)程之間得到的 count 值保持一致;但我們忽略了一個(gè)因素,如果一個(gè)對(duì)象經(jīng)常會(huì)被某一個(gè)線(xiàn)程操作,而被其他線(xiàn)程操作的頻次很少,那我們是不是可以給這一個(gè)類(lèi)似 "owner" 的線(xiàn)程一些特殊的優(yōu)化,即便讓其他的線(xiàn)程慢一點(diǎn)也影響不大?

而事實(shí)上,絕大多數(shù)對(duì)象都是面臨這樣一種情況。所以,這里我們就 “Biased” 了,讓 “owner” 線(xiàn)程的 Reference Counting 操作速度達(dá)到極致,而不用保證 Atomic ,只需要讓其他所有的線(xiàn)程 Atomic 即可(好吧,這里我也不是很懂,為什么 Non-Atomic 就一定比 Atomic 要快,但我知道為了做到 Atomic 顯然要做某些犧牲,等有時(shí)間我再具體看看為啥,然后補(bǔ)充到這里)。這一點(diǎn)非常關(guān)鍵,是整個(gè) nogil 項(xiàng)目對(duì)性能貢獻(xiàn)最大的一點(diǎn),我畫(huà)了個(gè)動(dòng)畫(huà)幫助理解:


 

Immortalization

上面的 Biased Reference Counting 好用的前提是“大多數(shù)變量只有一個(gè)線(xiàn)程會(huì)經(jīng)常使用”,但對(duì)于那些 0、1、True、False、 None之類(lèi)的變量呢?這些變量可是幾乎每一個(gè)線(xiàn)程都要頻繁使用的。為了提高這類(lèi)變量的操作速度,Sam 很巧妙地把這些變量 Immortalize(永久化)了,使得這類(lèi)變量的引用不再需要做計(jì)數(shù)!我看到了這里,就有種強(qiáng)烈的“md我怎么沒(méi)想到”的感覺(jué)。

不過(guò)實(shí)現(xiàn) Immortalization 也不是沒(méi)有犧牲的:計(jì)數(shù)值的 LSB(最低有效位,Least Significant Bit)不能再用了,因?yàn)?LSB 被用來(lái)代表這個(gè)變量是不是可以永久化掉了。這里會(huì)結(jié)合下面的 Deferred Reference Counting 再多討論一些。

Deferred Reference Counting

繼續(xù)揪著 Reference Counting 不放:那些既不能被永久化掉的同時(shí)又需要頻繁使用的對(duì)象怎么辦(怎么有點(diǎn)諧音梗...)?這個(gè)第二低有效位也被拿來(lái)征用了,被用來(lái)表示某個(gè)對(duì)象是否需要“Defer”它的引用計(jì)數(shù)。這個(gè)“Defer”的意思我個(gè)人感覺(jué)有一點(diǎn)誤導(dǎo),因?yàn)樗鋵?shí)并非“延后”,根本就是不再計(jì)數(shù)了,把所有釋放相關(guān)的工作都交給 GC (Garbage Collector)了,畢竟很多引用的 top-level functions 或者 modules 本來(lái)就是只能被 GC 給釋放掉。

這里的具體實(shí)現(xiàn)我也不是很懂,但知道大概是因?yàn)榫植孔兞恳话闶窃趦?nèi)存的 Stack 上,Deferred Reference Counting 是完全不用管 Stack 上的計(jì)數(shù)變化,但如果一個(gè)對(duì)象的引用是被放在 Heap 上的,這個(gè)時(shí)候計(jì)數(shù)其實(shí)是照常的,只不過(guò)不會(huì)因?yàn)?Heap 上的計(jì)數(shù)為 0 而直接釋放掉它,畢竟這個(gè)時(shí)候有可能有 Stack 內(nèi)存還在引用它。

Immortalization 和 Deferred Reference Counting 加起來(lái)一下就用掉了兩個(gè)最低位,也就是說(shuō)以后每次調(diào)用 Py_INCREF 和 Py_DECREF,Reference Count每次變化就是 4 了,感覺(jué)怪怪的。不過(guò)按 Sam 的原話(huà),這里其實(shí)變化是 1 還是 4 并不重要,畢竟我們大部分情況下只關(guān)心這個(gè)計(jì)數(shù)是不是零就夠了。這么說(shuō),也確實(shí)有些道理。

Mimalloc

Python 的內(nèi)存分配器 PyMalloc 被換成 mimalloc 了???mimalloc 文檔看到第二段就感覺(jué)好厲害:

mimalloc is a drop-in replacement for malloc and can be used in other programs without code changes

這哪里是換掉 PyMalloc,這原來(lái)是可以直接換掉 malloc 了。。。

具體實(shí)現(xiàn)細(xì)節(jié)我就沒(méi)有看了,因?yàn)槲抑牢铱隙床欢?。但是這里使用它的原因就很明顯了:因?yàn)?PyMalloc 有 GIL 的保護(hù),所以不需要也做不到 thread-safe,而 mimalloc 可以讓 Python 做到 thread-safe 同時(shí)性能大幅提升。

Collection Read-only Access

寫(xiě)到這里,終于寫(xiě)到了碼農(nóng)們熟悉的 list 和 dict 對(duì)象了。

當(dāng)我們引用或一個(gè) list 或 dict 對(duì)象,發(fā)生的過(guò)程大致可以簡(jiǎn)單地分成三個(gè)步驟:

 

  1. 加載這個(gè)對(duì)象的地址
  2. 修改對(duì)象的 Reference Count
  3. 返回這個(gè)對(duì)象的地址

 

這一切在 GIL 的保護(hù)下沒(méi)什么問(wèn)題。然鵝現(xiàn)在我們沒(méi)有 GIL 了,這里會(huì)出現(xiàn)一個(gè)問(wèn)題:當(dāng)有一個(gè)線(xiàn)程執(zhí)行寫(xiě)操作時(shí),在步驟 2 把這個(gè)對(duì)象釋放掉了(Reference Count 減少到 0),而這個(gè)時(shí)候又有一個(gè)線(xiàn)程已經(jīng)完成了步驟 1,開(kāi)始直接步驟 2 時(shí),就崩潰了,因?yàn)檫@個(gè)對(duì)象已經(jīng)被釋放掉了。

Sam 的設(shè)計(jì)是,既然我們沒(méi)有 GIL 這把全局鎖了,我們就要給單個(gè)對(duì)象加局部鎖,不過(guò)我們只對(duì)寫(xiě)操作加鎖。具體實(shí)現(xiàn)簡(jiǎn)單來(lái)說(shuō)就是增加了 Reference Counting 版本控制和更多的檢查判斷并重試機(jī)制,比如在執(zhí)行上述的步驟 2 時(shí)首先檢查對(duì)象是否 Reference Count 已經(jīng)為 0 了,如果是的話(huà),從步驟 1 開(kāi)始重試(重試之后我理解就可以讀到一個(gè)新的地址或是可以識(shí)別出是空地址從而保證安全性)。

目前對(duì)于 list 和 dict 的重新設(shè)計(jì),主要是對(duì)單線(xiàn)程處理速度進(jìn)行了優(yōu)化,對(duì)于多線(xiàn)程處理只能保證安全而速度上有一定程度的犧牲。也許以后會(huì)出現(xiàn)一些特殊的 collection 類(lèi)型,以應(yīng)對(duì)那種多線(xiàn)程頻繁調(diào)用的情況。

Python 4.0 是否真的會(huì)移除GIL?

我個(gè)人感覺(jué),出現(xiàn)一個(gè)沒(méi)有GIL版本的 Python 4.0 的可能性是比較大的,畢竟 CPython 核心團(tuán)隊(duì)其實(shí)已經(jīng)在著手將 Sam 大神的 nogil 項(xiàng)目合入 Python 3.11 了,而且該項(xiàng)目的性能分?jǐn)?shù)已經(jīng)達(dá)到甚至部分超過(guò)了 Guido 爸爸之前對(duì)于拿掉 GIL 的基本條件,這一次沒(méi)有“借口”可以拒絕了。當(dāng)然 Python 3.11 多半不會(huì)是一個(gè)無(wú) GIL 版本,nogil 項(xiàng)目無(wú)論多強(qiáng)大它也還只是個(gè)實(shí)驗(yàn)項(xiàng)目,其仍存在諸多大小問(wèn)題,以及很多仍待討論的架構(gòu)決策,都不是一個(gè)小版本就能夠解決掉的。

至于 Python 4.0,它自己本身就是個(gè)未知數(shù)。核心團(tuán)隊(duì)自己已經(jīng)重申了多次他們想盡量延后 Python 4.0 的時(shí)間,因?yàn)?Python 2.0 到 3.0 大家已經(jīng)很傷了,這么快又搞一波怕大家心里承受不了。。。Guido 爸爸很久就曾發(fā)推解釋過(guò)一次:


 

這里我想吐槽一下,從 Python 3.5 開(kāi)始,每個(gè)版本就已經(jīng)很傷了好么還不如趕緊上 nogil 也算是個(gè)痛并快樂(lè)著。

無(wú)論結(jié)果如何,我作為一個(gè)被 Python 領(lǐng)進(jìn)門(mén)的、被 Python 各種騷操作種草的、到現(xiàn)在不管后端服務(wù)還是客戶(hù)端腳本還是各種AI“小研究”都首選 Python的忠實(shí)粉絲,衷心祝愿 Python 未來(lái)...越來(lái)越妖!

分享到:
標(biāo)簽:Python 4.0
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定