基于棧的指令集與基于寄存器的指令集
Javac編譯器輸出的字節(jié)碼指令流,基本上是一種基于棧的指令集架構(gòu)(Instruction Set Architecture,ISA),字節(jié)碼指令流里面的指令大部分都是零地址指令,它們依賴操作數(shù)棧進(jìn)行工作。
與之相對(duì)的另外一套常用的指令集架構(gòu)師基于寄存器的指令集,最典型的就是x86的二地址指令集,通俗來(lái)講就是現(xiàn)在主流PC機(jī)中物理硬件直接支持的指令集架構(gòu),這些指令依賴寄存器進(jìn)行工作。
兩者之間的區(qū)別:
以“1+1”為例,
- 基于棧的指令集如下
iconst_1
iconst_1
iadd
istore_0
兩條iconst_1指令連續(xù)把另個(gè)常量1壓入棧后,iadd指令把棧頂?shù)膬蓚€(gè)值出棧、相加。然后把結(jié)果放回棧頂,最后istore_0把棧頂?shù)闹捣诺骄植孔兞勘淼牡?個(gè)變量槽中。
這種指令流中的指令通常都是不帶參數(shù)的,使用操作數(shù)棧中的數(shù)據(jù)作為指令的運(yùn)算輸入,指令的運(yùn)算結(jié)果也存儲(chǔ)在操作數(shù)棧之中。
- 基于寄存器的指令集
mov eax,1
add eax,1
mov指令把EAX寄存器的值設(shè)為1,然后add指令再把這個(gè)值加1,結(jié)果就保存在EAX寄存器里。這種二地址指令是x86指令集中的主流,每個(gè)指令都包含兩個(gè)單獨(dú)的輸入?yún)?shù),依賴于寄存器來(lái)訪問(wèn)和存儲(chǔ)數(shù)據(jù)。
基于棧的指令集主要優(yōu)點(diǎn)是可移植,因?yàn)榧拇嫫饔捎布苯犹峁?,程序直接依賴這些硬件寄存器則不可避免要受到硬件約束。如現(xiàn)在的32位80x86體系的處理器能提供了8個(gè)32位的寄存器,而ARMv6體系的處理器則提供了30個(gè)32位的通用寄存器,其中前16個(gè)用戶模式可使用。如使用棧架構(gòu)的指令集,用戶程序不會(huì)直接用到這些寄存器,那就可由虛擬機(jī)實(shí)現(xiàn)來(lái)自行決定把一些訪問(wèn)最頻繁的數(shù)據(jù)(程序計(jì)數(shù)器、棧頂緩存等)放到寄存器中以獲取盡量好的性能,這樣實(shí)現(xiàn)起來(lái)也更簡(jiǎn)單一些。棧機(jī)構(gòu)的指令集還有一些其它優(yōu)點(diǎn),如代碼相對(duì)更加緊湊(字節(jié)碼每個(gè)字節(jié)就對(duì)應(yīng)一條指令)、編譯器實(shí)現(xiàn)更加簡(jiǎn)單(不需考慮空間分配)等。
主要缺點(diǎn)是理論上執(zhí)行速度相對(duì)來(lái)說(shuō)會(huì)稍慢一些,所有主流物理機(jī)的指令集都是寄存器架構(gòu)也從側(cè)面印證了這點(diǎn)。不過(guò)這里的執(zhí)行速度就要局限于解析執(zhí)行的狀態(tài)下,如果經(jīng)過(guò)即時(shí)編譯器輸出成物理機(jī)上的匯編指令流,那就與虛擬機(jī)采用哪種指令的架構(gòu)沒(méi)什么關(guān)系。
在解釋執(zhí)行時(shí),棧架構(gòu)指令集的代碼雖然緊湊,但完成相同功能所需的指令數(shù)量一般會(huì)比寄存器架構(gòu)來(lái)的更多。因?yàn)槌鰲!⑷霔2僮鞅旧砭彤a(chǎn)生了相當(dāng)大量的指令。更重要的是棧實(shí)現(xiàn)的內(nèi)存中,頻繁的棧訪問(wèn)也就意味著頻繁的內(nèi)存訪問(wèn),相對(duì)于處理器來(lái)說(shuō),內(nèi)存始終是執(zhí)行速度的瓶頸。盡管虛擬機(jī)可采取棧頂緩存的優(yōu)化方法,把最常用的操作映射到寄存器中避免直接內(nèi)存訪問(wèn),但這也只是優(yōu)化措施而不是解決本質(zhì)問(wèn)題的方法。因此由于指令數(shù)量和內(nèi)存訪問(wèn)的原因,導(dǎo)致了棧架構(gòu)指令集的執(zhí)行速度會(huì)相對(duì)慢上一些。
《深入理解Java虛擬機(jī)》第三版學(xué)習(xí)