《python 源码剖析》 读后总结(虚拟机综述)
我是研究過(guò)jvm 所以在讀這本書(shū)的時(shí)候總是先關(guān)注 python 的虛擬機(jī)。
關(guān)注python 的虛擬機(jī),首先你要先了解 .py文件編譯之后,在python虛擬機(jī)中是什么的結(jié)構(gòu);換句話說(shuō),要知道pyc 二進(jìn)制文件字節(jié)碼的 格式,文件魔術(shù)、字符表、字符串,常量,模塊信息、字節(jié)碼、方法和變量等信息。
然后pyc在虛擬機(jī)中執(zhí)行時(shí)候,創(chuàng)建的對(duì)象信息是什么樣的,即pyCodeObject.如圖:
圖中的pyCodeObject包含子模塊(方法和類)和子pyCodeObject信息,這樣指針鏈接關(guān)系,構(gòu)成環(huán)境鏈,環(huán)境鏈(模塊鏈)對(duì)于python虛擬機(jī)而言非常重要,是執(zhí)行的關(guān)鍵!!
虛擬機(jī)是依據(jù)環(huán)境鏈需要和創(chuàng)建對(duì)應(yīng)的pyFrameObject對(duì)象和其對(duì)應(yīng)的棧結(jié)構(gòu)。
然后再去了解虛擬機(jī)的架構(gòu),內(nèi)存管理,垃圾回收,以及多線程的實(shí)現(xiàn)和一些機(jī)制。
對(duì)于虛擬機(jī)編譯py之后,在內(nèi)存上創(chuàng)建pycodeObject,在運(yùn)行的之后,同時(shí)會(huì)創(chuàng)建pyFrameObject對(duì)象。這個(gè)對(duì)象對(duì)應(yīng)的是一個(gè)棧結(jié)構(gòu),當(dāng)pycodeObject第一次使用的時(shí)候,就會(huì)被虛擬機(jī)創(chuàng)建運(yùn)行棧數(shù)據(jù)對(duì)象,pycodeObject和pyFrameObject一一對(duì)應(yīng)。pyFrameObject里面包含了pycodeObject,同時(shí)包含棧信息,執(zhí)行當(dāng)前環(huán)境鏈上的前一個(gè)frame信息,builtin、local、global名字空間,當(dāng)前字節(jié)碼指令信息(在源代碼的行數(shù)),上一個(gè)字節(jié)碼,需要內(nèi)存空間信息等。如圖:
虛擬機(jī)執(zhí)行過(guò)程中,在一個(gè)環(huán)境域中(作用域)執(zhí)行的結(jié)果要保存到對(duì)應(yīng)的pyFrameObject中,那么才能保證在不同的作用域取值的正確性,有效性。對(duì)于pyFrameObject的調(diào)用關(guān)系(pycodeObject中import)也會(huì)在pyFrameObject中的struck_frame *last_frame? 形成棧依賴(調(diào)用)鏈。我們寫(xiě)一個(gè)python文件,文件中直接聲明一個(gè)方法直接調(diào)用,python雖然也是面對(duì)對(duì)象的編程語(yǔ)言,但是對(duì)于開(kāi)發(fā)者是可以面對(duì)過(guò)程的開(kāi)發(fā)。只是虛擬機(jī)底層對(duì)函數(shù)進(jìn)行了封裝,函數(shù)也是對(duì)象(python中萬(wàn)物皆對(duì)象)。函數(shù)被封裝為pyFunctionObject,其中包含了參數(shù),函數(shù)名,func_doc,func_code,func_globals,func_module等等。func_code是指向func_code對(duì)象的指針,func_code也是對(duì)象,是代碼指令對(duì)象。func_module是函數(shù)可調(diào)用的modules;func_global是函數(shù)的上下文或者函數(shù)可調(diào)用的空間。函數(shù)的調(diào)用指針指向pyFunctionObject,再轉(zhuǎn)向pyCodeObject,其中涉及參數(shù)的傳遞,上下文的使用。
對(duì)于python類與對(duì)象,類有一個(gè)統(tǒng)一的締造者Object 類,j基類Object類有一個(gè)type類型的metaclass,或者可以說(shuō)是類對(duì)象模版。這點(diǎn)和python和javascript虛擬機(jī)很相似,js引擎對(duì)于方法和類都是有封裝的,都是一個(gè)對(duì)象,且在js引擎中,函數(shù)和類都分別有自己的頂層模版。對(duì)于python class 創(chuàng)建一個(gè)對(duì)象,首先是class對(duì)象的創(chuàng)建,然后根據(jù)這個(gè)class對(duì)象去創(chuàng)建instance 對(duì)象。class對(duì)象的創(chuàng)建是獲取pyTypeClassA(假如是classA類)的對(duì)象封裝,其中包含類繼承信息,class 對(duì)象在初始化的時(shí)候,調(diào)用type_new。這個(gè)方法首先做的就是獲取父類信息(python支持多繼承),然后調(diào)用metaclass模版去創(chuàng)建class對(duì)象。對(duì)于 對(duì)象instance的創(chuàng)建,虛擬機(jī)調(diào)用object_new 方法,直接獲取class對(duì)象,然后就是type_call 區(qū)執(zhí)行init初始化。
對(duì)于jvm 和 python 虛擬機(jī)有很多類似的地方,雖然兩者一個(gè)是編譯型語(yǔ)言,一個(gè)是腳本。但是這兩者都是有源碼解析生成中間文件,中間文件在被加載后生成字節(jié)碼文件對(duì)象,然后虛擬機(jī)后面的執(zhí)行都是和這個(gè)字節(jié)碼對(duì)象有關(guān)。區(qū)別在于,jvm中每一個(gè)字節(jié)碼文件就是class對(duì)象,在加載的時(shí)候就會(huì)創(chuàng)建類對(duì)象。而且方法是直接存儲(chǔ)在類對(duì)象中,不會(huì)單獨(dú)的封裝成方法對(duì)象。但是python的方法對(duì)象和類對(duì)象的管理和js引擎類似,萬(wàn)物皆對(duì)象,對(duì)類和方法以及作用域都進(jìn)行了對(duì)象的封裝。
對(duì)于java創(chuàng)建一個(gè)對(duì)象,首先到方法區(qū)的常量池中,尋找該對(duì)象的類類型,(全局常量,類的字符信息表都存在常量池中)然后檢測(cè)該類是不是已經(jīng)初始化了(有沒(méi)有創(chuàng)建類對(duì)象),如果是就是快速創(chuàng)建;否則就是慢創(chuàng)建。
快速創(chuàng)建:就是直接根據(jù)類對(duì)象創(chuàng)建,首先在eden區(qū),分配一塊地址,指針指向一塊空區(qū)間,設(shè)置空區(qū)間的大小(int* FirstAddress和size)。然后初始化對(duì)象頭信息,分代年齡,偏向id,偏向時(shí)間戳等。然后一些方法指針指向類對(duì)象信息,接著就是調(diào)整線程棧對(duì)象的指向,pc的調(diào)整。
慢創(chuàng)建:就是類沒(méi)有初始化,先將類進(jìn)行加載或者初始化,然后再創(chuàng)建。
對(duì)于js和python對(duì)象雖然不一定和java一致,但是大致過(guò)程肯定是一樣;首先應(yīng)該是符號(hào)的匹配,空間分配,初始化信息,調(diào)整引用指向。
對(duì)于php zend 虛擬機(jī)其對(duì)每一個(gè)php文件編譯生成對(duì)應(yīng)的指令數(shù)組oop_array和數(shù)據(jù)數(shù)組;對(duì)于php中的類,編譯之后生成對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),但是內(nèi)部還是只是存儲(chǔ)的屬性變量數(shù)組指針和方法列表指針。函數(shù)列表中每一個(gè)指針指向這個(gè)方法的指令數(shù)組的首地址。對(duì)于指令執(zhí)行的時(shí)候,啟動(dòng)zend.execute() c代碼函數(shù),將指令和數(shù)據(jù)指針交給函數(shù)去執(zhí)行。zend_execute 調(diào)用指令的方式有CALL, SWICH和GOTO方式,call是直接的函數(shù)調(diào)用,建立函數(shù)棧幀,指令一次出入棧。goto是直接調(diào)用指令,execute.mian將指令指針直接移交給EBP指令棧頂寄存器。swich方式 是指令在兩者之間的判斷選擇。對(duì)于zend虛擬機(jī),其內(nèi)部沒(méi)有建立棧幀,只是對(duì)于對(duì)象數(shù)據(jù)和代碼指令都是存儲(chǔ)在分配的內(nèi)存堆中。數(shù)據(jù)的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)主要就是hashtable和數(shù)組,指令的執(zhí)行是依據(jù)zend execute.main 建立的唯一的棧幀去執(zhí)行。
**************************************************************************************************************
插入說(shuō)一下JIT動(dòng)態(tài)編譯:
靜態(tài)編譯優(yōu)化和動(dòng)態(tài)編譯優(yōu)化最大的不同是他們?cè)诰幾g時(shí)所得到的信息量的不同。靜態(tài)編譯在運(yùn)行程序之前就把所有的執(zhí)行代碼編譯完,這時(shí)編譯器所接受的編譯信息量是不夠多的。比如說(shuō):某個(gè)函數(shù)是否是大量地被調(diào)用了,函數(shù)的實(shí)參是不是一直是一個(gè)常數(shù),等等。 動(dòng)態(tài)編譯之于靜態(tài)編譯,缺點(diǎn)是它需要即時(shí)編譯代碼,但是有一個(gè)優(yōu)點(diǎn)——編譯器可以獲得靜態(tài)編譯期所沒(méi)有的信息。比如:通過(guò)運(yùn)行時(shí)的profiling可以知道哪些函數(shù)是被大量使用的。在哪些execution path上哪些函數(shù)的參數(shù)一直都沒(méi)有變,等等。不要小看這些信息,當(dāng)即時(shí)編譯器了解這些信息之后可以在短時(shí)間內(nèi)編譯出比靜態(tài)編譯器更優(yōu)質(zhì)的二進(jìn)制碼。舉例來(lái)說(shuō),一般程序也遵循90-10原則,即運(yùn)行時(shí)的90%里計(jì)算機(jī)是在處理其中10%的代碼,尋找到這些執(zhí)行熱點(diǎn)代碼進(jìn)行深度優(yōu)化能得到比靜態(tài)編譯更好的性能(因?yàn)橐阎嘈畔⒘?#xff09;。? 然而現(xiàn)實(shí)是:即時(shí)編譯的開(kāi)銷非常大,暫時(shí)還不能超越靜態(tài)編譯的總體性能。不過(guò),一個(gè)動(dòng)態(tài)語(yǔ)言(如JAVA,Python)有著靜態(tài)語(yǔ)言(如C++)所沒(méi)有的各種優(yōu)勢(shì),必然是將來(lái)程序語(yǔ)言發(fā)展的方向。伴隨著強(qiáng)大的需求,即時(shí)編譯器在將來(lái)也會(huì)更加強(qiáng)大。
對(duì)于解釋器語(yǔ)言來(lái)說(shuō)(不管直接解釋 AST 還是字節(jié)碼),指令分發(fā)是個(gè)開(kāi)銷很大的過(guò)程(即一個(gè)很大的 switch case ,根據(jù)得到的指令決定解釋器下一步要做什么),這樣會(huì)導(dǎo)致這部分的 CPU 指令緩存命中率大幅下降。
對(duì)于動(dòng)態(tài)類型語(yǔ)言來(lái)說(shuō),JIT 時(shí)還可以做類型特化。比如一個(gè)函數(shù) add(x, y),純解釋的話,每次執(zhí)行時(shí)需要判斷 x 與 y 的類型,根據(jù)類型再做具體的操作(整數(shù)加法 / 浮點(diǎn)數(shù)加法 / 字符串拼接)。假使實(shí)際代碼運(yùn)行過(guò)程中,x 和 y 一直都是整型類型,這些操作也不能省略,導(dǎo)致性能下降。但是 JIT 的時(shí)候,可以將此函數(shù)編譯成 add_int_int(x: int, y: int) 的形式,這樣性能就和編譯型語(yǔ)言完全相等了。
函數(shù)內(nèi)聯(lián),JIT 的時(shí)候可以根據(jù)函數(shù)調(diào)用情況,將常用的一些函數(shù)做內(nèi)聯(lián)編譯。
**************************************************************************************************************
對(duì)于js虛擬機(jī)(例如 V8)它是基于JIT,沒(méi)有所謂的中間字節(jié)碼文件,在運(yùn)行時(shí)的時(shí)候會(huì)針對(duì)字節(jié)碼的解析和優(yōu)化處理(這樣可以獲取運(yùn)行時(shí)的處理信息,以達(dá)到性能提升)。
Chakra(Microsoft Internet Explorer)
Nitro/JavaScript Core (Safari)
Carakan (Opera)
SpiderMonkey (Firefox)
V8 (Chrome, Chromium)
總結(jié)
以上是生活随笔為你收集整理的《python 源码剖析》 读后总结(虚拟机综述)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: iphone的概览和配件用途详细介绍
- 下一篇: [WebKit] JavaScriptC