⑴ SET I1=100 指令的含義是將100的值賦給()
摘要 重排序後, a 的兩次操作被放到一起,指令執行情況變為 Load a、Set to 100、Set to 110、 Store a。下面和 b 相關的指令不變,仍對應 Load b、 Set to 5、Store b。
理論上的就不說了,你自己搜也能搜到很多。
舉個例子,你從一個方法a調用了另一個方法b。
我們知道,在a和b之中是可以創建相同名稱的變數的,比如都有int i = 0;這句話。這種現象的根本原因在於,方法的調用會產生中斷,中斷產生後,cpu會做現場保護,包括把變數等進行壓棧操作,即把方法a的相關資源進行了壓棧,而方法b的相關資源放在棧頂,只有棧頂資源可以與cpu交互(就把方法a中的變數i保護起來),當方法b結束後出棧,a就又回到了棧頂,並獲取了方法b運行的結果,然後繼續運行。
哎,有些啰嗦了。方法的調用、中斷、壓棧出棧等等這些操作你說一點不消耗資源吧,那是不可能的,多少都會消耗一些,雖然很非常十分微不足道。那麼編譯器的優化過程,我知道的其作用之一,就是會把這些做一個優化。原本方法a一共10句話,你偏要只寫1句,然後第2句寫成方法b,第3句寫成方法c。。。。。,然後依次嵌套調用。這樣的源代碼,編譯器優化後,就跟你直接寫10句是一個結果,即做了一定程度上的優化。
⑶ java指令重排序,happens-before的問題
不會的。
java代碼肯定是執行t.i = 1這個後,
再執行new Thread(t).start();這個
所以不會出現你說的情況
⑷ 編譯器的編譯器優化
應用程序之所以復雜, 是由於它們具有處理多種問題以及相關數據集的能力。實際上, 一個復雜的應用程序就象許多不同功能的應用程序「 粘貼」 在一起。源文件中大部分復雜性來自於處理初始化和問題設置代碼。這些文件雖然通常占源文件的很大一部分, 具有很大難度, 但基本上不花費C PU 執行周期。
盡管存在上述情況, 大多數Makefile文件只有一套編譯器選項來編譯項目中所有的文件。因此, 標準的優化方法只是簡單地提升優化選項的強度, 一般從O 2 到O 3。這樣一來, 就需要投人大量 精力來調試, 以確定哪些文件不能被優化, 並為這些文件建立特殊的make規則。
一個更簡單但更有效的方法是通過一個性能分析器, 來運行最初的代碼, 為那些佔用了85 一95 % CPU 的源文件生成一個列表。通常情況下, 這些文件大約只佔所有文件的1%。如果開發人員立刻為每一個列表中的文件建立其各自的規則, 則會處於更靈活有效的位置。這樣一來改變優化只會引起一小部分文件被重新編譯。進而,由於時間不會浪費在優化不費時的函數上, 重編譯全部文件將會大大地加快。
⑸ DSP編譯器如何進行優化
優化是一個很大的學問,c6000的編程工具指南(清華出版)大部分講的是這個,可以看看,貌似這本書不好買
⑹ C語言中Valatile關鍵字有什麼用
volatile提醒編譯器它後面所定義的變數隨時都有可能改變,因此編譯後的程序每次需要存儲或讀取這個變數的時候,都會直接從變數地址中讀取數據。如果沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,如果這個變數由別的程序更新了的話,將出現不一致的現象。下面舉例說明。在DSP開發中,經常需要等待某個事件的觸發,所以經常會寫出這樣的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}
這段程序等待內存變數flag的值變為1(懷疑此處是0,有點疑問,)之後才運行do2()。變數flag的值由別的程序更改,這個程序可能是某個硬體中斷服務程序。例如:如果某個按鈕按下的話,就會對DSP產生中斷,在按鍵中斷程序中修改flag為1,這樣上面的程序就能夠得以繼續運行。但是,編譯器並不知道flag的值會被別的程序修改,因此在它進行優化的時候,可能會把flag的值先讀入某個寄存器,然後等待那個寄存器變為1。如果不幸進行了這樣的優化,那麼while循環就變成了死循環,因為寄存器的內容不可能被中斷服務程序修改。為了讓程序每次都讀取真正flag變數的值,就需要定義為如下形式:
volatile short flag;
需要注意的是,沒有volatile也可能能正常運行,但是可能修改了編譯器的優化級別之後就又不能正常運行了。因此經常會出現debug版本正常,但是release版本卻不能正常的問題。所以為了安全起見,只要是等待別的程序修改某個變數的話,就加上volatile關鍵字。
volatile的本意是「易變的」
由於訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) do_something();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中斷產生時,在main當中調用do_something函數,但是,由於編譯器判斷在main函數裡面沒有修改過i,因此可能只執行一次對從i到某寄存器的讀操作,然後每次if判斷都只使用這個寄存器裡面的「i副本」,導致do_something永遠也不會被調用。如果變數加上volatile修飾,則編譯器保證對此變數的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
一般說來,volatile用在如下的幾個地方:
1、中斷服務程序中修改的供其它程序檢測的變數需要加volatile;
2、多任務環境下各任務間共享的標志應該加volatile;
3、存儲器映射的硬體寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
另外,以上這幾種情況經常還要同時考慮數據的完整性(相互關聯的幾個標志讀了一半被打斷了重寫),在1中可以通過關中斷來實現,2中可以禁止任務調度,3中則只能依靠硬體的良好設計了。
二、volatile 的含義
volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變數在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合並,常量傳播等優化,進一步可以死代碼消除。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化,volatile的字面含義是易變的,它有下面的作用:
1 不會在兩個操作之間把volatile變數緩存在寄存器中。在多任務、中斷、甚至setjmp環境下,變數可能被其他的程序改變,編譯器自己無法知道,volatile就是告訴編譯器這種情況。
2 不做常量合並、常量傳播等優化,所以像下面的代碼:
volatile int i = 1;
if (i > 0) ...
if的條件不會當作無條件真。
3 對volatile變數的讀寫不會被優化掉。如果你對一個變數賦值但後面沒用到,編譯器常常可以省略那個賦值操作,然而對Memory Mapped IO的處理是不能這樣優化的。
前面有人說volatile可以保證對內存操作的原子性,這種說法不大准確,其一,x86需要LOCK前綴才能在SMP下保證原子性,其二,RISC根本不能對內存直接運算,要保證原子性得用別的方法,如atomic_inc。
對於jiffies,它已經聲明為volatile變數,我認為直接用jiffies++就可以了,沒必要用那種復雜的形式,因為那樣也不能保證原子性。
你可能不知道在Pentium及後續CPU中,下面兩組指令
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一條指令反而不如三條指令快。
三、編譯器優化 → C關鍵字volatile → memory破壞描述符zz
「memory」比較特殊,可能是內嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的優化知識,再看C關鍵字volatile。最後去看該描述符。
1、編譯器優化介紹
內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬體上引入硬體高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬體級別的優化。再看軟體一級的優化:一種是在編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變數緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬體重新排序引起的問題的解決辦法是在從硬體(或者其他處理器)的角度看必須以特定順序執行的操作之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。
void Barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬體無效,編譯後的代碼會把當前CPU寄存器中的所有修改過的數值存入內存,需要這些數據的時候再重新從內存中讀出。
2、C語言關鍵字volatile
C語言關鍵字volatile(注意它是用來修飾變數而不是上面介紹的__volatile__)表明某個變數的值可能在外部被改變,因此對這些變數的存取不能緩存到寄存器,每次使用時需要重新存取。該關鍵字在多線程環境下經常使用,因為在編寫多線程的程序時,同一個變數可能被多個線程修改,而程序通過該變數同步各個線程,例如:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast<int*>(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
該線程啟動時將intSignal 置為2,然後循環等待直到intSignal 為1 時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際運行的時候該線程卻不會退出,即使在外部將它的值改為1,看一下對應的偽匯編代碼就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
對於C編譯器來說,它並不知道這個值會被其他線程修改。自然就把它cache在寄存器裡面。記住,C 編譯器是沒有線程概念的!這時候就需要用到volatile。volatile 的本意是指:這個值可能會在當前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關鍵字,這時候,編譯器知道該變數的值會在外部改變,因此每次訪問該變數時會重新讀取,所作的循環變為如下面偽碼所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知識就不難理解Memory修改描述符了,Memory描述符告知GCC:
1)不要將該段內嵌匯編指令與前面的指令重新排序;也就是在執行內嵌匯編代碼之前,它前面的指令都執行完畢
2)不要將變數緩存到寄存器,因為這段代碼可能會用到內存變數,而這些內存變數會以不可預知的方式發生改變,因此GCC插入必要的代碼先將緩存到寄存器的變數值寫回內存,如果後面又訪問這些變數,需要重新訪問內存。
如果匯編指令修改了內存,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時就需要在修改描述部分增加「memory」,告訴GCC 內存已經被修改,GCC 得知這個信息後,就會在這段指令之前,插入必要的指令將前面因為優化Cache 到寄存器中的變數值先寫回內存,如果以後又要使用這些變數再重新讀取。
使用「volatile」也可以達到這個目的,但是我們在每個變數前增加該關鍵字,不如使用「memory」方便。
⑺ C++編譯器(Dev-C)是否會自動內聯函數 對於什麼樣的函數即使標記inline也會拒絕內聯
G++編譯器是否會自動進行內聯函數?
G++編譯器是很先進的,編譯的時候如果開啟優化,G++會代碼進行各種優化,如:對合適的函數進行內聯(即便是沒有添加inline關鍵字),對某些函數直接對其進行求值,除此之外G++編譯器還可以對代碼進行重排序 等等。編譯器比你更了解硬體,所以只要允許它優化,他會盡量進行優化。你使用的Dev C++集成開發環境使用的c++編譯器就是G++。
什麼樣的函數即使標記inline也無法內聯?
比如函數體太大、太復雜的話(比如包含多重循環、包含遞歸調用),對其進行內聯得不償失,這時編譯器就會忽略inline關鍵字,VC++編譯器提供了強制內聯函數的關鍵字,除非你非常了解硬體,不然最好讓編譯器來處。編譯不對那些函數進行內聯要看具體的編譯器實現了。
inline關鍵字的有哪些作用?
inline關鍵字可以提示編譯器對某個函數進行內聯,並且強制函數使用內部鏈接。比如說你在頭文件定義了某個函數,為了防止多重定義,你可以添加inline關鍵字來防止多重定義錯誤。
如果對硬體不是很了解,底層的代碼優化還是留給編譯器來處理。
看看下面的幾個編譯器優化函數的例子:
1.編譯器直接對函數求值:
解釋一下:
第一條和第二天指令分別將b和a的地址載入到寄存器rdx和rcx中
第三條指令將b的值載入到eax寄存器中
第四條指令將34存入b中
第五條指令將eax的值加1(eax保存了之前b的值)
第六條指令將eax的值存入a中
可以看出編譯器將函數的兩條語句換了位置,這種優化主要是優化代碼的執行速度,有的CPU內存讀寫操作的的開銷不一樣,所以重新排序一下某些代碼能夠提高程序執行速度。
⑻ 編譯器 優化
編譯是從源代碼(通常為高階語言)到能直接被計算機或虛擬機執行的目標代碼(通常為低階語言或機器語言)的翻譯過程。然而,也存在從低階語言到高階語言的編譯器,這類編譯器中用來從由高階語言生成的低階語言代碼重新生成高階語言代碼的又被叫做反編譯器。也有從一種高階語言生成另一種高階語言的編譯器,或者生成一種需要進一步處理的的中間代碼的編譯器(又叫級聯)。
典型的編譯器輸出是由包含入口點的名字和地址, 以及外部調用(到不在這個目標文件中的函數調用)的機器代碼所組成的目標文件。一組目標文件,不必是同一編譯器產生,但使用的編譯器必需採用同樣的輸出格式,可以鏈接在一起並生成可以由用戶直接執行的可執行程序。
從他的原理我們就好優化了,但是方法很多的
⑼ java中虛擬機的內存到底分為幾類呢,網上說法挺多,能不能給個專業的
Java內存模型
主內存與工作內存
Java內存模型的主要目標是定義程序中各個變數的訪問規則,即在虛擬機中將變數存儲到內存和從內存中取出變數這樣底層細節。此處的變數與Java編程時所說的變數不一樣,指包括了實例欄位、靜態欄位和構成數組對象的元素,但是不包括局部變數與方法參數,後者是線程私有的,不會被共享。
Java內存模型中規定了所有的變數都存儲在主內存中,每條線程還有自己的工作內存(可以與前面將的處理器的高速緩存類比),線程的工作內存中保存了該線程使用到的變數到主內存副本拷貝,線程對變數的所有操作(讀取、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變數。不同線程之間無法直接訪問對方工作內存中的變數,線程間變數值的傳遞均需要在主內存來完成,線程、主內存和工作內存的交互關系如下圖所示
這里的主內存、工作內存與Java內存區域的Java堆、棧、方法區不是同一層次內存劃分。
內存間交互操作
關於主內存與工作內存之間的具體交互協議,即一個變數如何從主內存拷貝到工作內存、如何從工作內存同步到主內存之間的實現細節,Java內存模型定義了以下八種操作來完成:
· lock(鎖定):作用於主內存的變數,把一個變數標識為一條線程獨占狀態。
· unlock(解鎖):作用於主內存變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他線程鎖定。
· read(讀取):作用於主內存變數,把一個變數值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
· load(載入):作用於工作內存的變數,它把read操作從主內存中得到的變數值放入工作內存的變數副本中。
· use(使用):作用於工作內存的變數,把工作內存中的一個變數值傳遞給執行引擎,每當虛擬機遇到一個需要使用變數的值的位元組碼指令時將會執行這個操作。
· assign(賦值):作用於工作內存的變數,它把一個從執行引擎接收到的值賦值給工作內存的變數,每當虛擬機遇到一個給變數賦值的位元組碼指令時執行這個操作。
· store(存儲):作用於工作內存的變數,把工作內存中的一個變數的值傳送到主內存中,以便隨後的write的操作。
· write(寫入):作用於主內存的變數,它把store操作從工作內存中一個變數的值傳送到主內存的變數中。
如果要把一個變數從主內存中復制到工作內存,就需要按順尋地執行read和load操作,如果把變數從工作內存中同步回主內存中,就要按順序地執行store和write操作。Java內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是可以插入其他指令的,如對主內存中的變數a、b進行訪問時,可能的順序是read a,read b,load b, load a。Java內存模型還規定了在執行上述八種基本操作時,必須滿足如下規則:
· 不允許read和load、store和write操作之一單獨出現
· 不允許一個線程丟棄它的最近assign的操作,即變數在工作內存中改變了之後必須同步到主內存中。
· 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。
· 一個新的變數只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變數。即就是對一個變數實施use和store操作之前,必須先執行過了assign和load操作。
· 一個變數在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
· 如果對一個變數執行lock操作,將會清空工作內存中此變數的值,在執行引擎使用這個變數前需要重新執行load或assign操作初始化變數的值
· 如果一個變數事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變數。
· 對一個變數執行unlock操作之前,必須先把此變數同步到主內存中(執行store和write操作)。
重排序
在執行程序時為了提高性能,編譯器和處理器經常會對指令進行重排序。重排序分成三種類型:
編譯器優化的重排序。編譯器在不改變單線程程序語義放入前提下,可以重新安排語句的執行順序。
指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
內存系統的重排序。由於處理器使用緩存和讀寫緩沖區,這使得載入和存儲操作看上去可能是在亂序執行。
從Java源代碼到最終實際執行的指令序列,會經過下面三種重排序:
為了保證內存的可見性,Java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。Java內存模型把內存屏障分為LoadLoad、LoadStore、StoreLoad和StoreStore四種:
同步機制
介紹volatile、synchronized和final
原子性、可見性與有序性
Java內存模型JMM解決了可見性和有序性的問題,而鎖解決了原子性的問題。
可見性
指的是一個線程對變數的寫操作對其他線程後續的讀操作可見。由於現代CPU都有多級緩存,CPU的操作都是基於高速緩存的,而線程通信是基於內存的,這中間有一個Gap,可見性的關鍵還是在對變數的寫操作之後能夠在某個時間點顯示地寫回到主內存,這樣其他線程就能從主內存中看到最新的寫的值。volatile,synchronized(隱式鎖), 顯式鎖,原子變數這些同步手段都可以保證可見性。
可見性底層的實現是通過加內存屏障實現的:
1. 寫變數後加寫屏障,保證CPU寫緩沖區的值強制刷新回主內存
2. 讀變數之前加讀屏障,使緩存失效,從而強制從主內存讀取變數最新值
寫volatile變數 = 進入鎖
讀volatile變數 = 釋放鎖
有序性
指的是數據不相關的變數在並發的情況下,實際執行的結果和單線程的執行結果是一樣的,不會因為重排序的問題導致結果不可預知。volatile, final, synchronized,顯式鎖都可以保證有序性。
有序性的語意有幾層,
1. 最常見的就是保證多線程執行的串列順序
2. 防止重排序引起的問題
3. 程序執行的先後順序,比如JMM定義的一些Happens-before規則
重排序
的問題是一個單獨的主題,常見的重排序有3個層面:
1. 編譯級別的重排序,比如編譯器的優化
2. 指令級重排序,比如CPU指令執行的重排序
3. 內存系統的重排序,比如緩存和讀寫緩沖區導致的重排序
原子性
是指某個(些)操作在語意上是原子的。比如讀操作,寫操作,CAS(compareand set)操作在機器指令級別是原子的,又比如一些復合操作在語義上也是原子的,如先檢查後操作if(xxx== null){}
有個專有名詞競態條件來描述原子性的問題。
競態條件(racing condition)是指某個操作由於不同的執行時序而出現不同的結果,比如先檢查後操作。
volatile變數只保證了可見性,不保證原子性,比如a++這種操作在編譯後實際是多條語句,比如先讀a的值,再加1操作,再寫操作,執行了3個原子操作,如果並發情況下,另外一個線程很有可能讀到了中間狀態,從而導致程序語意上的不正確。所以a++實際是一個復合操作。
加鎖可以保證復合語句的原子性,sychronized可以保證多條語句在synchronized塊中語意上是原子的。
顯式鎖保證臨界區的原子性。
原子變數也封裝了對變數的原子操作。
非阻塞容器也提供了原子操作的介面,比如putIfAbsent。