C 編譯器優化

2022-09-08 07:03:16 字數 2683 閱讀 8482

正如在第一篇文章中提到的,編譯器可能通過對記憶體操作進行重新排序來優化**。 在 .net framework 4.5 中,將 c# 編譯為 il 的 csc.exe 編譯器並不執行大量的優化操作,因此該編譯器不會對記憶體操作進行重新排序。 但將 il 編譯為機器碼的實時 (jit) 編譯器實際上將執行一些對記憶體操作進行重新排序的優化,我將在下文對此予以介紹。

請考慮下面的輪詢迴圈模式:

c#

class

test

).start();

//poll the _flag field until it is set to false

while

(_flag) ;

//the loop might never terminate!

}}

在這個示例中,.net 4.5 jit 編譯器可能按如下所示重寫迴圈:

c#

if (_flag)

對於單執行緒而言,此項轉換完全合法,並且將讀取提公升出迴圈通常是一種出色的優化方法。 但如果在另乙個執行緒上將 _flag 設定為 false,則優化可能導致掛起。

請注意,如果 _flag 欄位是可變字段,則 jit 編譯器不會將讀取提公升出迴圈。 (有關對此模式更詳細的介紹,請參見我在十二月發表的文章中的「輪詢迴圈」部分。)

以下示例說明了另乙個可能導致多執行緒**出現錯誤的編譯器優化:

c#

class

test

}

此類包含兩個非可變字段:_a 和 _b。 方法 foo 先讀取字段 _a,然後讀取字段 _b。 但由於這兩個字段是非可變字段,因此編譯器可自由地對兩個讀取進行重新排序。 因此,如果演算法的正確與否取決於讀取順序,則程式將包含錯誤。

很難想象編譯器通過交換讀取順序將獲得什麼結果。 根據 foo 的編寫方式,編譯器可能不會交換讀取順序。

但如果我在 foo 方法的頂部再新增乙個無關緊要的語句,則確實會進行重新排序:

c#

public

bool

foo()

在 foo 方法的第一行上,編譯器將 _b 的值載入到暫存器中。 然後,_b 的第二次載入將僅使用暫存器中已有的值,而不發出實際的載入指令。

實際上,編譯器將按如下所示重寫 foo 方法:

c#

public

bool

foo()

儘管此**示例大體上比較接近編譯器優化**的方式,但了解一下此**的反彙編也很有指導意義:

c#

if (_b == -1) throw

newexception();

push eax

mov edx,dword ptr [ecx+8]

//load field _b into edx register

cmp edx,0ffffffffh

je

00000016

int a =_a;

mov eax,dword ptr [ecx+4]

//load field _a into eax register

return a >b;

cmp eax,edx

//compare registers eax and edx

...

即使您不了解彙編,也很容易理解以上**中所執行的操作。 在計算條件 _b == -1 的過程中,編譯器將字段 _b 載入到 edx 暫存器中。 此後再次讀取字段 _b 時,編譯器僅重用 edx 中已有的值,而不發出實際的記憶體讀取指令。 因此,_a 和 _b 的讀取被重新排序。

在此示例中,正確的解決方案是將字段 _a 標記為可變字段。 如果完成此項標記,編譯器便不會對 _a 和 _b 的讀取進行重新排序,因為 _a 的載入具有載入-獲取語義。 但需要指出的是,.net framework(版本 4 以及早期的版本)不會正確地處理此示例,實際上將字段 _a 標記為可變字段不會禁止讀取重新排序。 .net framework 4.5 版已修復此問題。

正如我剛剛介紹的,編譯器有時會將多個讀取融合為乙個讀取。 編譯器還可以將單個讀取拆分為多個讀取。 在 .net framework 4.5 中,讀取引入與讀取消除相比並不常用,並僅在極少數的特定情況下才會發生。 但它有時確實會發生。

要了解讀取引入,請考慮以下示例:

c#

public

class

readintro

} void

uninitialize()

}

如果檢視一下 printobj 方法,會發現 obj.tostring 表示式中的 obj 值似乎永遠不會為 null。 但實際上該行**可能會引發 nullreferenceexception。 clr jit 可能會對 printobj 方法進行編譯,就好像它是用以下**編寫的:

c#

void

printobj()

}

由於 _obj 欄位的讀取已經拆分為該字段的兩個讀取,因此 tostring 方法現在可能在乙個值為 null 的目標上被呼叫。

請注意,在 x86-x64 上的 .net framework 4.5 中,您無法使用此**示例重現 nullreferenceexception。 讀取引入很難在 .net framework 4.5 中重現,但它確實會在某些特殊情況下發生。

C 編譯器優化

1 volatile 順序性 兩個包含volatile變數的指令,編譯後不可以亂序。注意是編譯後不亂序,但是在執行的過程中還是可能會亂序的,這點需要由其它機制來保證,例如memory barriers。不可優化性 volatile告訴編譯器,不要對這個變數進行各種激進的優化,甚至將變數直接消除,保證...

C 編譯器優化

1 volatile 順序性 兩個包含volatile變數的指令,編譯後不可以亂序。注意是編譯後不亂序,但是在執行的過程中還是可能會亂序的,這點需要由其它機制來保證,例如memory barriers。不可優化性 volatile告訴編譯器,不要對這個變數進行各種激進的優化,甚至將變數直接消除,保證...

編譯器優化

常量摺疊 a 1 2 由於結果可預見,編譯器直接生成a 3 常量傳播a 1 若後續 沒有更改a,則編譯器將a直接用其值1代替 減少變數 對於x和y的比較,可以轉換成if i j x i2 y j 2 if x y 複寫傳播 類似於常量長傳,不過傳播的是變數 若後續 未修改a的值,則編譯器用m代替a ...