32位和64位CPU與指令重排


在本文回答這幾個焦點問題,主要介紹一下32位和64位系統區別和聯系,64位的優點和判斷方法,及在選購64位系統硬軟件時的幾點建議僅供交流參考

一、32位和64位的區別

1、32位和64位一般是指CPU的通用寄存器位寬,所以64位的CPU位寬增加一倍

2、可尋址范圍大大擴展,32位系統支持最大內存位4G,64位系統理論支持最大內存2^64=18446,744,073,709,551,616,約1600萬TB,相當於16EB。(實際還受制於操作系統和主板約束),實際的CPU尤其是這兩年的CPU都是采取兼容設計的,內部總線不到64位,后期的CPU逐漸都會采用標准的64位,具體見下文。

3、32位系統和64位系統需要安裝支持相應系統模式下的操作系統和驅動軟件,也就是32位只能安裝32位,64位安裝64位的但可兼容32位運算。

4、目前約定俗成的x86就是代表32位操作系統,x64代表64位操作系統,天緣博客中出現很多操作系統標示比如(x86)——代表32位,(x64)——代表64位。

5、目前64位CPU標准有:AMD 64、EMT-64、IA-64。更多關於INTEL 64:http://zh.wikipedia.org/zh-cn/Intel_64

二、64位系統的優點

64位系統的理論優點:

*64位系統理論支持安裝最大16EB的內存,具體跟CPU的地址總線寬度有關,地址總線寬度及支持內存大小見下面的表格

*所有64位寄存器仍然使用相同的划分方案,仍支持執行8位運算

*RIP(新的64位指令指針)替代32位的EIP指針(再早期的IP指針為16位),並向下兼容。

*SIMD指令使用新的寄存器,CPU在64位模式下有16、64位MMX寄存器

* XMM寄存器為16位,用來做SSE浮點運算指針

*只有FPU寄存器是80位寬度,其它剩余的寄存器和指令都是64位寬度

 更多請參考:http://www.tech-faq.com/cpu.shtml

CPU 地址總線寬度:

 

CPU 地址總線
8086 20 bit
8088 20 bit
80286 24 bit
80386SX 24 bit
80386DX 32 bit
80486SX 32 bit
80486DX 32 bit
Pentium I 32 bit
K6 32 bit
Duron 32 bit
Athlon 32 bit
Athlon XP 32 bit
Celeron 36 bit
Pentium Pro 36 bit
Pentium II 36 bit
Pentium III 36 bit
Pentium 4 36 bit
Athlon 40 bit
Athlon-64 40 bit
Athlon-64 FX 40 bit
Opteron 40 bit
Itanium 44 bit
Itanium 2 44 bit

 

更多CPU地址總線寬度:http://www.cpu-world.com/CPUs/CPU.html

地址總線可尋址范圍(支持最大內存):

 

地址總線寬度 最大內存
20 bits 1MB
24 bits 16MB
32 bits 4GB
36 bits 64GB
40 bits 1TB
44 bits 16TB

 

64位CPU的實用優點:

64位的系統在視頻編輯、文件搜索、科學計算、人工智能、平面設計、視頻處理、3D動畫和游戲、數據庫以及各種網絡服務器等方面具備更強大的優勢,尤其是在工程制圖、3D、音視頻制作等領域的具有極佳的應用效果。64位系統效能發揮需要三大模塊支撐:硬件、操作系統、上層軟件。普通的32位軟件是無法在64位操作系統上運行的。

三、如何判斷您的計算機是否支持64位操作系統

硬件上的區分:

1、CPU: AMD在2003年春季發布第一款針對服務器的x86架構64位服務器處理器皓龍,秋天發布了同樣架構的速龍64系列處理器,揭開了64位運算新篇章(當然這是指桌面處理器,專業的64位甚至更高位的處理器早就有了,比如SUN公司的UltraSparc Ⅲ、IBM公司的POWER5、HP公司的Alpha等)。2005年4月26日,微軟正式發布了64位操作系統Windows Server 2003 x64 Edition和Windows XP Professional x64 Edition,距離第一片64位x86處理器上市有兩年多時間。

也就是說目前發布的通用處理器,包括INTEL、AMD幾乎都是64位的,天緣認為只要主板不是“偷工減料”的采用兼容性設計,都是可以支持64位操作系統安裝,當然實際支持內存的大小還決定CPU的地址總線寬度和您的主板情況。實際運行還跟前端總線有關,比如早期的FSB和Core i5、Core i7使用的QPI(對抗AMD的HT總線)。更多QPI相關知識:http://baike.baidu.com/view/1377507.htm

2、主板:目前市面的主流主板都是支持64位CPU的。

3、內存:基本無限制,當然為了搭配新最新的CPU和主板,速度上最好不要拖后腿就可以了。

軟件上區分:

1、操作系統:目前Windows的大部分系列都有對應的64位版本發布。比如Windows 7除了家庭版初級班沒有64位,其它都有,windows Server 2008 SP2只發布64位版本。

2、驅動程序和軟件:這一點最為頭疼,從目前來看,驅動程序除了部分老設備(比如打印機、掃描儀較老可能會沒有64位驅動),新的設備都會發布64位驅動程序,但是64位的軟件就非常缺乏,而且很多企業由於應用市場關系,目前64位應用還沒到非用不可的地步,所以很多企業都還未開發64位版本軟件,即使開發出來,售價也相當高。這種現狀可能會持續稍后的3-5年不會一下改觀。

平台測試:

比如使用CPU-Z等軟件查看CPU是否支持EMT-64指令集即可。EMT-64本來是專指INTEL CPU支持64位指令集,現在也指AMD 64了,如果顯示是AMD64也可以。此外還有IA-64,是INTEL獨立開發的64位處理器,不兼容32位計算機,是純的64位技術。謹慎選用

更多關於EMT64知識:http://www.tech-faq.com/em64t.shtml

五、關於64位系統的其它問題綜合

1、我是裝32位操作系統運行快,還是64位操作系統運行快?

從總體運行效率看,肯定是32位系統快,對於系統硬件配置不是太高端的用戶,天緣推薦仍然安裝32位操作系統。

更多關於運行速度的分析,請參考:

到底是32位系統運行快還是64位系統快

2、我是否有必要安裝64位操作系統?

這里,天緣還是保守一點,對於做科學運算、工程制圖、3D制作、音頻視頻編輯的用戶,天緣推薦嘗試安裝64位操作系統,那樣更能彰顯64位的優勢。

3、商家總是推薦支持64位是怎么回事?

大家不要相信商家所謂的64位支持,純粹是尋找賣點而已,現在不支持64位系統的已經很少,包括主板、CPU等等。

指令重排的原因以及可能造成的問題

為何要指令重排?       

現在的CPU一般采用流水線來執行指令。一個指令的執行被分成:取指、譯碼、訪存、執行、寫回、等若干個階段。然后,多條指令可以同時存在於流水線中,同時被執行。
指令流水線並不是串行的,並不會因為一個耗時很長的指令在“執行”階段呆很長時間,而導致后續的指令都卡在“執行”之前的階段上。
相反,流水線是並行的,多個指令可以同時處於同一個階段,只要CPU內部相應的處理部件未被占滿即可。比如說CPU有一個加法器和一個除法器,那么一條加法指令和一條除法指令就可能同時處於“執行”階段, 而兩條加法指令在“執行”階段就只能串行工作。
相比於串行+阻塞的方式,流水線像這樣並行的工作,效率是非常高的。

然而,這樣一來,亂序可能就產生了。比如一條加法指令原本出現在一條除法指令的后面,但是由於除法的執行時間很長,在它執行完之前,加法可能先執行完了。再比如兩條訪存指令,可能由於第二條指令命中了cache而導致它先於第一條指令完成。
一般情況下,指令亂序並不是CPU在執行指令之前刻意去調整順序。CPU總是順序的去內存里面取指令,然后將其順序的放入指令流水線。但是指令執行時的各種條件,指令與指令之間的相互影響,可能導致順序放入流水線的指令,最終亂序執行完成。這就是所謂的“順序流入,亂序流出”。

指令流水線除了在資源不足的情況下會卡住之外(如前所述的一個加法器應付兩條加法指令的情況),指令之間的相關性也是導致流水線阻塞的重要原因。
CPU的亂序執行並不是任意的亂序,而是以保證程序上下文因果關系為前提的。有了這個前提,CPU執行的正確性才有保證。比如:

a++; b=f(a); c--;

由於b=f(a)這條指令依賴於前一條指令a++的執行結果,所以b=f(a)將在“執行”階段之前被阻塞,直到a++的執行結果被生成出來;而c--跟前面沒有依賴,它可能在b=f(a)之前就能執行完。(注意,這里的f(a)並不代表一個以a為參數的函數調用,而是代表以a為操作數的指令。C語言的函數調用是需要若干條指令才能實現的,情況要更復雜些。)

像這樣有依賴關系的指令如果挨得很近,后一條指令必定會因為等待前一條執行的結果,而在流水線中阻塞很久,占用流水線的資源。而編譯器的亂序,作為編譯優化的一種手段,則試圖通過指令重排將這樣的兩條指令拉開距離, 以至於后一條指令進入CPU的時候,前一條指令結果已經得到了,那么也就不再需要阻塞等待了。比如將指令重排為:

a++; c--; b=f(a);

相比於CPU的亂序,編譯器的亂序才是真正對指令順序做了調整。但是編譯器的亂序也必須保證程序上下文的因果關系不發生改變。

 

指令重排可能產生的問題:

1.無法識別帶有隱式因果關系的指令

 

有些程序邏輯,單純從上下文是看不出它們的因果關系的。比如:

*addr=5; val=*data;

從表面上看,addr和data是沒有什么聯系的,完全可以放心的去亂序執行。但是如果這是在某某設備驅動程序中,這兩個變量卻可能對應到設備的地址端口和數據端口。並且,這個設備規定了,當你需要讀寫設備上的某個寄存器時,先將寄存器編號設置到地址端口,然后就可以通過對數據端口的讀寫而操作到對應的寄存器。那么這么一來,對前面那兩條指令的亂序執行就可能造成錯誤。

2.多線程帶來的問題

考慮下面的代碼:

線程A:

 

 c=10;

 b=c;

 flag=true;

線程B:

while(flag){

System.out.println(b);

}

如果編譯器將線程A的第三條指令重排到第一行,那線程B拿到的b的數據就有可能出錯了。

JVM之指令重排分析

引言:在Java中看似順序的代碼在JVM中,可能會出現編譯器或者CPU對這些操作指令進行了重新排序;在特定情況下,指令重排將會給我們的程序帶來不確定的結果.....

1.  什么是指令重排?

      在計算機執行指令的順序在經過程序編譯器編譯之后形成的指令序列,一般而言,這個指令序列是會輸出確定的結果;以確保每一次的執行都有確定的結果。但是,一般情況下,CPU和編譯器為了提升程序執行的效率,會按照一定的規則允許進行指令優化,在某些情況下,這種優化會帶來一些執行的邏輯問題,主要的原因是代碼邏輯之間是存在一定的先后順序,在並發執行情況下,會發生二義性,即按照不同的執行邏輯,會得到不同的結果信息。

 2.  數據依賴性

    主要指不同的程序指令之間的順序是不允許進行交互的,即可稱這些程序指令之間存在數據依賴性。

    主要的例子如下:

  1.  
    名稱 代碼示例 說明
  2.  
    寫后讀 a = 1;b = a; 寫一個變量之后,再讀這個位置。
  3.  
    寫后寫 a = 1;a = 2; 寫一個變量之后,再寫這個變量。
  4.  
    讀后寫 a = b;b = 1; 讀一個變量之后,再寫這個變量。

 進過分析,發現這里每組指令中都有寫操作,這個寫操作的位置是不允許變化的,否則將帶來不一樣的執行結果。

 

  編譯器將不會對存在數據依賴性的程序指令進行重排,這里的依賴性僅僅指單線程情況下的數據依賴性;多線程並發情況下,此規則將失效。

3.  as-if-serial語義

   不管怎么重排序(編譯器和處理器為了提高並行度),(單線程)程序的執行結果不能被改變。編譯器,runtime 和處理器都必須遵守as-if-serial語義。

   分析:  關鍵詞是單線程情況下,必須遵守;其余的不遵守。

   代碼示例:

  1.  
    double pi = 3.14; //A
  2.  
    double r = 1.0; //B
  3.  
    double area = pi * r * r; //C

 分析代碼:   A->C  B->C;  A,B之間不存在依賴關系; 故在單線程情況下, A與B的指令順序是可以重排的,C不允許重排,必須在A和B之后。
結論性的總結:

 

   as-if-serial語義把單線程程序保護了起來,遵守as-if-serial語義的編譯器,runtime 和處理器共同為編寫單線程程序的程序員創建了一個幻覺:單線程程序是按程序的順序來執行的。as-if-serial語義使單線程程序員無需擔心重排序會干擾他們,也無需擔心內存可見性問題。

   核心點還是單線程,多線程情況下不遵守此原則。

4.  在多線程下的指令重排

     首先我們基於一段代碼的示例來分析,在多線程情況下,重排是否有不同結果信息:

  1.  
    class ReorderExample {
  2.  
    int a = 0;
  3.  
    boolean flag = false;
  4.  
     
  5.  
    public void writer() {
  6.  
    a = 1; //1
  7.  
    flag = true; //2
  8.  
    }
  9.  
     
  10.  
    Public void reader() {
  11.  
    if (flag) { //3
  12.  
    int i = a * a; //4
  13.  
    ……
  14.  
    }
  15.  
    }
  16.  
    }

上述的代碼,在單線程情況下,執行結果是確定的, flag=true將被reader的方法體中看到,並正確的設置結果。 但是在多線程情況下,是否還是只有一個確定的結果呢?

 

假設有A和B兩個線程同時來執行這個代碼片段, 兩個可能的執行流程如下:

    可能的流程1, 由於1和2語句之間沒有數據依賴關系,故兩者可以重排,在兩個線程之間的可能順序如下: 

  

   可能的流程2:, 在兩個線程之間的語句執行順序如下:

  

    

根據happens- before的程序順序規則,上面計算圓的面積的示例代碼存在三個happens- before關系:

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

這里的第3個happens- before關系,是根據happens- before的傳遞性推導出來的 

    在程序中,操作3和操作4存在控制依賴關系。當代碼中存在控制依賴性時,會影響指令序列執行的並行度。為此,編譯器和處理器會采用猜測(Speculation)執行來克服控制相關性對並行度的影響。以處理器的猜測執行為例,執行線程B的處理器可以提前讀取並計算a*a,然后把計算結果臨時保存到一個名為重排序緩沖(reorder buffer ROB)的硬件緩存中。當接下來操作3的條件判斷為真時,就把該計算結果寫入變量i中。從圖中我們可以看出,猜測執行實質上對操作3和4做了重排序。重排序在這里破壞了多線程程序的語義。

     核心點是:兩個線程之間在執行同一段代碼之間的critical area,在不同的線程之間共享變量;由於執行順序、CPU編譯器對於程序指令的優化等造成了不確定的執行結果。

5.  指令重排的原因分析

    主要還是編譯器以及CPU為了優化代碼或者執行的效率而執行的優化操作;應用條件是單線程場景下,對於並發多線程場景下,指令重排會產生不確定的執行效果。

6.  如何防止指令重排

    volatile關鍵字可以保證變量的可見性,因為對volatile的操作都在Main Memory中,而Main Memory是被所有線程所共享的,這里的代價就是犧牲了性能,無法利用寄存器或Cache,因為它們都不是全局的,無法保證可見性,可能產生臟讀。
    volatile還有一個作用就是局部阻止重排序的發生,對volatile變量的操作指令都不會被重排序,因為如果重排序,又可能產生可見性問題。
    在保證可見性方面,鎖(包括顯式鎖、對象鎖)以及對原子變量的讀寫都可以確保變量的可見性。但是實現方式略有不同,例如同步鎖保證得到鎖時從內存里重新讀入數據刷新緩存,釋放鎖時將數據寫回內存以保數據可見,而volatile變量干脆都是讀寫內存。

7.  可見性

    這里提到的可見性是指前一條程序指令的執行結果,可以被后一條指令讀到或者看到,稱之為可見性。反之為不可見性。這里主要描述的是在多線程環境下,指令語句之間對於結果信息的讀取即時性。

8.  參考文獻

    •      http://www.infoq.com/cn/articles/java-memory-model-2
    •      http://www.cnblogs.com/chenyangyao/p/5269622.html

volatile指令重排案例分析

volatile指令重排案例分析

大廠面試題:

1、請你談談對volatile的理解?

2、CAS你知道嗎?

3、原子類AtomicInteger的ABA問題談談?原子更新引用知道嗎?

4、我們都知道ArrayList是線程不安全的,請編碼寫一個不安全的案例並給出解決方案?

5、公平鎖/非公平鎖/可重入鎖/遞歸鎖/自旋鎖談談你的理解?請手寫一個自旋鎖。

6、CountDownLatch、CyclicBarrier、Semaphore使用過嗎?

7、阻塞隊列知道嗎?

8、線程池用過嗎?ThreadPoolExecutor談談你的理解?

9、線程池用過嗎?生產上你是如何設置合理參數?

10、死鎖編碼及定位分析?

 

1、volatile概念

    volatile是java虛擬機提供的輕量級同步機制

    volatile三個特性:

  • 保證可見性
  • 不保證原子性
  • 禁止指令重排

 

2、volatile禁止指令重排

(1)指令重排有序性:

計算機在執行程序時,為了提高性能,編譯器和處理器常常會做指令重排,一般分為以下三種:

單線程環境里面確保程序最終執行結果和代碼順序執行結果一致。

處理器在進行指令重排序時必須考慮指令之間的數據依賴性

多線程環境中線程交替執行,由於編譯器指令重排的存在,兩個線程使用的變量能否保證一致性是無法確認的,結果無法預測。

指令重排案例分析one:

    public void mySort() {

        int x = 11; // 語句1

        int y = 12; // 語句2

        x = x + 5; // 語句3

        y = x * x; // 語句4

    }

    // 指令重排之后,代碼執行順序有可能是以下幾種可能?

    // 語句1 -> 語句2 -> 語句3 -> 語句4

    // 語句1 -> 語句3 -> 語句2 -> 語句4

    // 語句2 -> 語句1 -> 語句3 -> 語句4

    // 問題:請問語句4可以重排后變為第1條嗎?

    // 不能,因為處理器在指令重排時必須考慮指令之間數據依賴性。

指令重排案例分析two:

指令重排案例分析three:

public class BanCommandReSortSeq {

    int a = 0; 

    boolean flag = false; 

 

    public void methodOne() {

        a = 1;  // 語句1

        flag = true;    // 語句2

 

        // methodOne發生指令重排,程序執行順序可能如下:

        // flag = true;    // 語句2

        // a = 1;  // 語句1

    }

 

    public void methodTwo() {

        if (flag) {

            a = a + 5;  // 語句3

        }

        System.out.println("methodTwo ret a = " + a);

    }

    // 多線程環境中線程交替執行,由於編譯器指令重排的存在,兩個線程使用的變量能否保證一致性是無法確認的,結果無法預測。

    // 多線程交替調用會出現如下場景:

    // 線程1調用methodOne,如果此時編譯器進行指令重排

    // methodOne代碼執行順序變為:語句2(flag=true) -> 語句1(a=5)

    // 線程2調用methodTwo,由於flag=true,如果此時語句1還沒有執行(語句2 -> 語句3 -> 語句1 ),那么執行語句3的時候a的初始值=0

    // 所以最終a的返回結果可能為 a = 0 + 5 = 5,而不是我們認為的a = 1 + 5 = 6;

}

(2)禁止指令重排底層原理:

       volatile實現禁止指令重排優化,從而避免多線程環境下程序出現亂序執行的現象。

 

       先了解下概念,內存屏障(Memory Barrier)又稱內存柵欄,是一個CPU指令,它的作用有兩個:

  • 保證特定操作執行的順序性;
  • 保證某些變量的內存可見性(利用該特性實現volatile內存可見性)

 

volatile實現禁止指令重排優化底層原理:

       由於編譯器和處理器都能執行指令重排優化。如果在指令間插入一條Memory Barrier則會告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排,也就是說通過插入內存屏障,就能禁止在內存屏障前后的指令執行重排優化。內存屏障另外一個作用就是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。

左邊:寫操作場景:先LoadStore指令,后LoadLoad指令。

右邊:讀操作場景:先LoadLoad指令,后LoadStore指令。

3、volatile使用場景

單例模式(DCL-Double Check Lock雙端檢鎖機制)

 

如果此時你也把volatile禁止指令重排底層原理也解釋清楚了,面試官可能會接着問你,你知道volatile使用場景嗎?

單例模式(DCL-Double Check Lock雙端檢鎖機制)就是它的使用場景

參考答案請看下一小節:單例模式下多線程是不安全


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM