每個程序員應知曉的編譯器優化相關內容

此文轉載。原文地址:https://msdn.microsoft.com/zh-cn/magazine/dn904673.aspx


高級編程語言提供了許多抽象的編程構造(如函數、條件語句和循環),可以讓我們獲得令人驚訝的工作效率。但是,在高級編程語言中編寫代碼的一個缺點是性能可能顯著降低。理想情況下,您應編寫易於理解、可維護的代碼 — 同時不影響性能。出於此原因,編譯器將嘗試自動優化代碼以提高其性能,如今在這方面的操作已經相當複雜。他們可以轉換循環、條件語句和遞歸函數;消除整個代碼塊;並且利用目標指令集體系結構 (ISA) 使代碼變得快速緊湊。更好的方法是專注於編寫易於理解的代碼,而手動優化會導致含義模糊、難於維護的代碼。實際上,手動優化代碼可能會阻止編譯器執行其他或效率更高的優化操作。

不必手動優化代碼,您應考慮設計方面,如使用更快的算法、合併線程級並行性和使用特定於框架的功能(如使用移動構造函數)。

本文介紹了 Visual C++ 編譯器優化。下面我要討論最重要的優化技術,以及爲了應用這些技術編譯器必須做出的決策。目的並不是告訴您如何手動優化代碼,而是向您說明爲什麼可以信賴編譯器來代表您優化代碼。本文絕非由 Visual C++ 編譯器執行的完整優化檢查。但是,本文介紹了您真正想要了解的優化內容,以及如何通過與編譯器通信來應用優化。

還介紹了超越當前任何編譯器功能的其他重要優化 — 例如,將無效算法替換爲有效算法,或更改數據結構佈局改善其局部性。但是,此類優化都超出了本文的討論範圍。

定義編譯器優化

優化是將一段代碼轉換爲其他功能上等效的代碼以提高一個或多個特徵的過程。兩個最重要的特徵是代碼速度和大小。其他特徵包括執行代碼所需的能量、編譯代碼所花費的時間、生成的代碼需要實時 (JIT) 編譯的情況,以及 JIT 編譯代碼所花費的時間。

編譯器不斷改善用於優化代碼的技術。但是,它們不是完美的。當然,與其花費時間手動調整程序,通常更富有成效的做法是,使用編譯器提供的特定功能並讓編譯器對代碼進行調整。

有四種方法來幫助編譯器更有效地優化您的代碼:

  1. 編寫易於理解、可維護的代碼。不應將 Visual C++ 的面向對象的功能視爲性能的敵人。最新版本的 Visual C++ 可以將此類開銷保持在最低限度,並有時可以完全消除開銷。
  2. 使用編譯器指令。例如,告知編譯器使用函數調用約定,速度比默認的更快。
  3. 使用編譯器內部函數。內部函數是一個由編譯器自動提供其實現的特殊函數。編譯器特別瞭解此函數,並將函數調用替換爲利用目標 ISA 的效率極高的指令序列。目前,Microsoft.NET Framework 不支持內部函數,因此任何託管語言均不支持內部函數。但是,Visual C++ 可以廣泛支持此功能。請注意,雖然使用內部函數可提高代碼的性能,但會減少其可讀性和可移植性。
  4. 使用按配置優化 (PGO)。使用此技術,編譯器更瞭解代碼在運行時的行爲方式,並對其進行相應優化。

本文的目的是通過演示在效率低下但可理解的代碼上執行優化(應用第一種方法)來向您說明爲什麼可以信任編譯器。此外,我將對按配置優化作簡短介紹,並提到一些可以微調代碼某些部分的編譯器指令。

有許多編譯器優化技術,包括簡單轉換(如常量摺疊)和極端轉換(如指令調度)等。但是,在本文中,我將僅對某些最重要的優化進行討論 — 這些優化可以顯著(通過兩位數的百分比表示)提高性能並減少代碼大小:函數內聯、COMDAT 優化和循環優化。我將在下一節中討論前兩個,然後向您演示如何控制由 Visual C++ 執行的優化。最後,我將簡單介紹下 .NET Framework 中的優化。在整篇文章中,我將使用 Visual Studio 2013 構建代碼。

鏈接時代碼生成

鏈接時代碼生成 (LTCG) 是一種對 C/C++ 代碼執行全程序優化 (WPO) 的技術。C/C++ 編譯器單獨編譯每個源文件,並生成相應的對象文件。這意味着編譯器僅可將優化應用於單個源文件,而不是應用於整個程序。但是,可以僅通過查看整個程序執行一些重要的優化。可以在鏈接時(而不是在編譯時)應用這些優化,因爲鏈接器具有該程序的完整視圖。

啓用 LTCG(通過指定 /GL 編譯器開關)後,編譯器驅動程序 (cl.exe) 將僅調用編譯器前端(c1.dll 或 c1xx.dll),並將其後端 (c2.dll) 的工作推遲到鏈接時。生成的對象文件包含 C 中間語言 (CIL) 代碼,而不包含依賴於計算機的程序集代碼。然後,當調用鏈接器 (link.exe) 時,會認爲對象文件包含 CIL 代碼,並調用編譯器後端,而後執行 WPO、生成二進制對象文件,並返回到鏈接器以整合所有對象文件並生成可執行文件。

前端實際上會執行一些優化(如常量合併),而不考慮是否已啓用或禁用優化。但是,所有的重要優化由編譯器後端執行,並且可以使用編譯器開關進行控制。

LTCG 可以使後端主動執行許多優化(通過指定 /GL 與 /O1 或 /O2 和 /Gw 編譯器開關以及 /OPT:REF 和 /OPT:ICF 鏈接器開關執行)。在本文中,我將只討論函數內聯和 COMDAT 優化。請參閱本文檔,獲取 LTCG 優化的完整列表。請注意,該鏈接器可以對本機對象文件、混合本機/託管對象文件、純託管對象文件、安全的託管對象文件和安全的 .netmodules 執行 LTCG。

我將構建一個包含兩個源文件(source1.c 和 source2.c)和一個頭文件 (source2.h) 的程序。source1.c 和 source2.c 文件,分別如圖 1圖 2 中所示。頭文件非常簡單,包含 source2.c 中所有函數的原型,因此本文將不對其進行介紹。

圖 1 source1.c 文件

#include <stdio.h> // scanf_s and printf.
#include "Source2.h"
int square(int x) { return x*x; }
main() {
  int n = 5, m;
  scanf_s("%d", &m);
  printf("The square of %d is %d.", n, square(n));
  printf("The square of %d is %d.", m, square(m));
  printf("The cube of %d is %d.", n, cube(n));
  printf("The sum of %d is %d.", n, sum(n));
  printf("The sum of cubes of %d is %d.", n, sumOfCubes(n));
  printf("The %dth prime number is %d.", n, getPrime(n));
}

圖 2 source2.c 文件

#include <math.h> // sqrt.
#include <stdbool.h> // bool, true and false.
#include "Source2.h"
int cube(int x) { return x*x*x; }
int sum(int x) {
  int result = 0;
  for (int i = 1; i <= x; ++i) result += i;
  return result;
}
int sumOfCubes(int x) {
  int result = 0;
  for (int i = 1; i <= x; ++i) result += cube(i);
  return result;
}
static
bool isPrime(int x) {
  for (int i = 2; i <= (int)sqrt(x); ++i) {
    if (x % i == 0) return false;
  }
  return true;
}
int getPrime(int x) {
  int count = 0;
  int candidate = 2;
  while (count != x) {
    if (isPrime(candidate))
      ++count;
  }
  return candidate;
}


source1.c 文件包含兩個函數:square 函數(獲取一個整數並返回其平方值)和該程序的 main 函數。main 函數調用 square 函數和 source2.c 的所有函數(isPrime 除外)。Source2.c 文件包含五個函數:cube 函數返回給定整數的立方值;sum 函數返回從 1 到給定整數的所有整數之和;sumOfcubes 函數返回從 1 到給定整數的所有整數立方和;isPrime 函數確定給定的整數是否爲質數;getPrime 函數返回 xth 質數。我省略了錯誤檢查,因爲這不是本文的要點。

該代碼簡單但很有用。有許多執行簡單計算的函數;有一些需要簡單的 for 循環。getPrime 函數最複雜,因爲它包含了一個 while 循環,並在循環中調用 isPrime 函數,其中 isPrime 函數還包含一個循環。我將使用此代碼來說明其中一個最重要的編譯器優化(即函數內聯)和一些其他優化。

我將構建三種不同配置的代碼,並檢查結果以確定編譯器如何對該代碼進行轉換。如果您繼續進行,您將需要彙編程序輸出文件(通過 /FA[s] 編譯器開關生成)來檢查生成的程序集代碼,並需要映射文件(通過 /MAP 鏈接器開關生成)來確定已執行的 COMDAT 優化(如果使用 /verbose:icf 和 /verbose:ref 開關,鏈接器還可以對此進行報告)。因此請確保在以下我討論的所有配置中指定這些開關。此外,我將使用 C 編譯器 (/TC),以便更輕鬆地檢查生成的代碼。不過,我在這裏討論的所有內容也適用於 C++ 代碼。

調試配置

使用調試配置的主要原因是,當指定 /Od 編譯器開關而未指定 /GL 開關時,將禁用所有後端優化。當在此配置下構建代碼時,生成的對象文件將包含與源代碼完全對應的二進制代碼。您可以檢查生成的彙編程序輸出文件和映射文件,以確認這一點。此配置等同於 Visual Studio 的調試配置。

編譯時代碼生成發佈配置

此配置類似於已啓用優化的發佈配置(通過指定 /O1、/O2 或 /Ox 編譯器開關啓用),而無需指定 /GL 編譯器開關。在此配置下,生成的對象文件將包含優化的二進制代碼。但是,不會在整個程序級別執行任何優化。

通過檢查生成的 source1.c 程序集列表文件,您會注意到已執行兩個優化。首先,圖 1 中首次調用的 square 函數(即 square(n))已在編譯時通過運算求值完全消除。爲什麼會這樣呢?編譯器判定 square 函數比較小,因此應進行內聯。內聯之後,編譯器判定本地變量 n 的值已知,且不會在賦值語句和函數調用之間更改。因此,得出的結論是,可以安全地執行乘法並替換結果 (25)。在第二個優化中,第二次調用的 square 函數(即 square(m))同樣已被內聯。但是,由於在編譯時 m 值是未知的,編譯器無法運算求值,因此發出實際代碼。

現在,我將檢查 source2.c 程序集列表文件,這會變得更加有意義。對 sumOfCubes 中 cube 函數的調用已經內聯。反過來,這已使編譯器可以在循環(正如“循環優化”一節所示)上執行重要的優化。此外,SSE2 指令集正用於 isPrime 函數,以在調用 sqrt 函數時將 int 轉換爲 double,還可以在從 sqrt 返回時將 double 轉換爲 int。並且在循環開始之前,sqrt 只調用一次。請注意,如果沒有將 /arch 開關指定給編譯器,則默認情況下 x86 編譯器使用 SSE2。大多數部署的 x86 處理器以及所有 x86-64 處理器支持 SSE2。

鏈接時代碼生成發佈配置

LTCG 發佈配置與 Visual Studio 中的發佈配置相同。在此配置中,啓用了優化,並指定了 /GL 編譯器開關。在使用 /O1 或 /O2 時,隱式指定此開關。這會告知編譯器發出 CIL 對象文件,而不是程序集對象文件。這樣,鏈接器調用編譯器後端來執行 WPO,如前文所述。現在,我將介紹幾個 WPO 優化,以顯示 LTCG 的巨大優點。使用此配置生成的程序集代碼列表可在線獲取。

只要啓用函數內聯(/Ob,請求優化時均會開啓),/GL 開關就會允許編譯器內聯其他轉換單元中定義的函數,無論是否指定了 /Gy 編譯器開關(稍後討論)。/LTCG 鏈接器開關是可選的,並僅提供有關該鏈接器的指導。

通過檢查 source1.c 程序集文件列表,您可以看到除 scanf_s 之外的所有函數調用都已經內聯。因此,編譯器就能夠執行 cube、sum 和 sumOfCubes 計算。只有 isPrime 函數還沒有內聯。但是,如果它已經在 getPrime 中手動內聯,編譯器仍將在 main 中內聯 getPrime。

如您所見,函數內聯非常重要,不只是因爲它優化了函數調用,而且還因爲它使編譯器能夠最終執行許多其他優化。內聯函數通常會提高性能,但是會增加代碼大小。過多使用這種優化會導致出現代碼膨脹現象。在每個調用站點,編譯器執行成本/收益分析,然後決定是否內聯函數。

由於內聯的重要性,Visual C++ 編譯器提供了比標準所規定的更多的內聯控制支持。您可以通過使用 auto_inline 雜注來告訴編譯器永遠不內聯的函數範圍。通過使用 __declspec(noinline) 標記一個特定函數或方法,您可以告知編譯器永遠不內聯此特定函數或方法。您可以使用內聯關鍵字標記函數,來提示編譯器內聯此函數(但是如果內聯將爲淨虧損,編譯器可以選擇忽略此提示)。內聯關鍵字自第一版 C++ 問世以來便可用,它在 C99 中引入。您可在 C 和 C++ 代碼中使用 Microsoft 專用關鍵字 __inline;在使用不支持此關鍵字的舊版本 C 時,這是很有用的。此外,您可以使用 __forceinline 關鍵字(C 和 C++),以強制編譯器在任何可能的情況下始終內聯函數。最後同樣重要的是,通過使用 inline_recursion 雜注內聯遞歸函數,您可以告知編譯器展開遞歸函數到特定深度或無限深度。請注意,編譯器當前不提供使您能夠在調用站點上(而不是在函數定義上)控制內聯的功能。

/Ob0 開關完全禁用內聯,默認情況下生效。調試時應使用此開關(在 Visual Studio 調試配置中自動指定此開關)。/Ob1 開關告知編譯器僅考慮用於內聯且標記有 inline、__inline 或 __forceinline 的函數。/Ob2 開關在指定 /O[1|2|x] 時生效,此開關告知編譯器考慮所有要內聯的函數。在我看來,使用內聯或 __inline 關鍵字的唯一原因是通過 /Ob1 開關控制內聯。

在某些情況下,編譯器將不能內聯函數。其中一種情況是,以虛擬方式調用虛函數;因爲編譯器可能不知道要調用哪個函數,所以無法內聯該函數。另一種情況是,通過指向該函數的指針(而不是使用其名稱)調用函數。若要啓用內聯,應該努力避免出現這種情況。有關此類情況的完整列表,請參閱 MSDN 文檔。

在用於整個程序級別時,函數內聯並不是最有效的優化。實際上,大多數優化在該級別都變得更加高效。在本節的其餘部分中,我將討論一類特定的優化,即 COMDAT 優化。

默認情況下,在編譯轉換單元時,所有代碼都存儲在所生成對象文件的單個部分中。鏈接器在部分級別上操作。也就是說,它可以刪除、合併以及重新排列各部分。這將阻止鏈接器執行三個優化,可以顯著(兩位數的百分比)減小可執行文件的大小並提高其性能。第一個是消除未引用的函數和全局變量。第二個是摺疊相同的函數和常量全局變量。第三個是重新排列函數和全局變量的順序,以便位於相同執行路徑的函數和共同訪問的變量在內存中的物理位置更接近,從而提高定域性。

若要啓用這些鏈接器優化,通過分別指定 /Gy(函數級鏈接)和 /Gw(全局數據優化)編譯器開關,可以告知編譯器將函數和變量打包到單個部分。這些部分稱爲 COMDAT。此外,還可以使用 __declspec( selectany) 標記特定的全局數據變量,以告知編譯器將變量打包到 COMDAT。然後,通過指定 /OPT:REF 鏈接器開關,鏈接器將消除未引用的函數和全局變量。此外,通過指定 /OPT:ICF 開關,鏈接器將摺疊相同的函數和全局常量變量。(ICF 代表相同的 COMDAT 摺疊。)使用 /ORDER 鏈接器開關,您可以指示鏈接器按特定的順序將 COMDAT 放置到生成的圖像。請注意,所有這些優化均是鏈接器優化,不需要 /GL 編譯器開關。在出於顯而易見的原因進行調試時,應禁用 /OPT:REF 和 /OPT:ICF 開關。

應儘可能使用 LTCG。不使用 LTCG 的唯一情況是,分發生成的對象和庫文件的情況。正如上文所述,這些文件包含 CIL 代碼,而不包含程序集代碼。CIL 代碼僅可供生成其相同版本的編譯器/鏈接器使用,這會極大地限制對象文件的可用性,因爲開發人員必須具有相同版本的編譯器來使用這些文件。在這種情況下,除非您願意對每個編譯器版本分發對象文件,否則您應使用編譯時代碼生成。除了有限的可用性之外,這些對象文件的大小要比相應的程序集對象文件大許多倍。但是,請記住,CIL 對象文件的巨大優勢是啓用 WPO。

循環優化

Visual C++ 編譯器支持多個循環優化,但我將只討論以下三個:循環展開、自動向量化和循環不變量代碼移動。如果您修改圖 1 中的代碼,以將 m 傳遞給 sumOfCubes 而不是傳遞 n,則編譯器將無法確定參數的值,因此必須編譯函數來處理所有參數。生成的函數經高度優化且大小相當大,因此編譯器不會內聯此函數。

使用 /O1 開關編譯代碼,會產生空間進行了優化的程序集代碼。在這種情況下,將不會對 sumOfCubes 函數執行任何優化。使用 /O2 開關編譯會產生速度進行了優化的代碼。代碼的大小會明顯變大但速度顯著變快,因爲 sumOfCubes 內的循環已展開且向量化。請務必明白,如果沒有內聯 cube 函數,則不可以向量化。此外,如果沒有內聯,則循環展開不會那麼高效。生成的程序集代碼的簡化圖形表示形式,如圖 3 中所示。X86 和 x86-64 體系結構的流向圖相同。

SumOfCubes 控制流向圖
圖 3 SumOfCubes 控制流向圖

圖 3 中,綠色菱形是入口點,紅色矩形是出口點。藍色菱形代表在運行時正在作爲 sumOfCubes 函數的一部分執行的條件。如果處理器支持 SSE4,且 x 大於或等於 8,那麼將使用 SSE4 指令同時執行四個乘法運算。同時對多個值執行相同運算的過程稱爲向量化。此外,編譯器將展開循環兩次;也就是說,在每次迭代中將重複兩次循環主體。組合的效果是,將爲每次迭代執行八個乘法運算。當 x 小於 8 時,將使用傳統的指令來執行計算的其餘部分。請注意,編譯器已在函數中發出三個(而不僅僅是一個)包含單獨結語的出口點。這將減少跳轉的次數。

循環展開是在循環內重複循環主體的過程,以便在已展開循環的單個迭代中執行多個循環迭代。這樣可以提高性能的原因是,將不再頻繁執行循環控制指令。或許更重要的是,它可能會使編譯器能夠執行許多其他優化,例如向量化。展開的缺點在於它會增加代碼大小和註冊壓力。但是,它可以以兩位數的百分比提高性能,具體取決於循環主體。

與 x86 處理器不同、所有 x86-64 處理器均支持 SSE2。此外,通過指定 /arch 開關,可以利用 Intel 和 AMD 最新 x86-64 微體系結構的 AVX/AVX2 指令集。同樣,指定 /arch:AVX2 可以使編譯器能夠使用 FMA 和 BMI 指令集。

目前,Visual C++ 編譯器無法讓您控制循環展開。但是,可以通過結合使用模板與 __ forceinline 關鍵字模擬這種技術。可以通過使用帶有 no_vector 選項的循環雜注禁用特定循環上的自動向量化。

通過查看生成的程序集代碼,敏銳的眼睛會注意到可以進一步優化代碼。但是,編譯器已經做得非常好,並且不會在分析代碼和應用次要優化方面花費過多的時間。

SomeOfCubes 不是唯一一個已展開循環的函數。如果您修改代碼,以便將 m 傳遞到 sum 函數(而不是傳遞 n),則編譯器將無法對該函數求值,因此它必須發出其代碼。在這種情況下,該循環將展開兩次。

我將討論的最後一個優化是循環不變量代碼移動。請考慮以下代碼段:

int sum(int x) {
  int result = 0;
  int count = 0;
  for (int i = 1; i <= x; ++i) {
    ++count;
    result += i;
  }
  printf("%d", count);
  return result;
}


此處的唯一更改是,我有一個附加的變量在每次迭代中均會增加,然後打印。不難看到,可以通過將計數變量增量移出循環來優化此代碼。也就是說,我可以只將 x 分配給計數變量。此優化稱爲循環不變量代碼移動。循環不變量部分清楚地表明瞭這種技術僅在代碼不依賴於任何循環標頭中的表達式時纔有效。

現在有個問題要注意:如果您手動應用這種優化,則生成的代碼可能會在某些情況下出現性能下降現象。您能找出其中的原因嗎?請考慮當 x 爲非正數時,會發生什麼情況。循環永遠不會執行,這意味着在未優化的版本中,不會接觸變量計數。但是,在手動優化的版本中,會不必要的在循環外部將 x 賦值給計數。此外,如果 x 爲負,則計數將保留錯誤值。人類和編譯器均易受這種缺陷的影響。幸運的是,通過在賦值之前發出循環條件,Visual C++ 編譯器就會有足夠的智能意識到這種缺陷,從而提高 x 所有值的性能。

總之,如果您既沒有編譯器也不是編譯器優化專家,則應避免手動轉換到代碼,這只是爲了使其看起來更快。請勿手動操作,要信任編譯器可以優化您的代碼。

控制優化

除了編譯器開關 /O1、/O2 和 /Ox 之外,您還可以使用類似以下的優化雜注控制特定函數的優化:

#pragma optimize( "[optimization-list]", {on | off} )


優化列表可以是空的,也可以包含一個或多個下列值:g、s、t 和 y。它們分別對應於編譯器開關 /Og、/Os、/Ot 和 /Oy。

具有 off 參數的空列表會導致關閉所有這些優化,無論是否已指定編譯器開關。具有 on 參數的空列表會使指定的編譯器開關生效。

/Og 開關可以啓用全局優化,即通過只查看優化的函數便可以執行這些優化,而不是查看它調用的所有函數。如果啓用了 LTCG,則 /Og 會啓用 WPO。

如果您希望採用不同的方式優化不同的函數(一些優化空間而另一些優化速度),則優化雜注將非常有用。但是,如果您確實想要具有該級別的控制,則應考慮按配置優化 (PGO),即通過在運行檢測版代碼時使用包含行爲信息的配置文件來優化代碼的過程。編譯器使用該配置文件來更好地決定如何優化代碼。Visual Studio 提供了必要的工具來將此技術應用於本機和託管代碼。

.NET 中的優化

在 .NET 編譯模型中不涉及任何鏈接器。但是,有一個源代碼編譯器(C# 編譯器)和一個 JIT 編譯器。源代碼編譯器僅執行次要優化。例如,它不會執行函數內聯和循環優化。相反,JIT 編譯器會處理這些優化。隨附 .NET Framework 4.5 及之前所有版本的 JIT 編譯器不支持 SIMD 指令。但是,隨附 .NET Framework 4.5.1 和更高版本的 JIT 編譯器(即 RyuJIT)支持 SIMD。

在優化功能方面,RyuJIT 和 Visual C++ 之間的區別是什麼?因爲 RyuJIT 會在運行時工作,所以它可以執行 Visual C++ 無法執行的優化。例如,在運行時,RyuJIT 可能能夠確定,在應用程序的此特定運行中,if 語句的條件永遠不會是 true,因此可以將其優化。此外,RyuJIT 可以利用它正在其中運行的處理器的功能。例如,如果處理器支持 SSE4.1,JIT 編譯器將只對 sumOfcubes 函數發出 SSE4.1 指令,從而使生成的代碼更爲緊湊。但是,它不能花太多時間優化代碼,因爲 JIT 編譯所花費的時間長短會影響應用程序的性能。而另一方面,Visual C++ 編譯器可花費更多的時間找出和利用其他優化機會。.NET Native 是 Microsoft 研發的優秀新技術,通過使用 Visual C++ 後端,.NET Native 可以將託管代碼編譯爲優化的獨立可執行文件。目前,這一技術僅支持 Windows 應用商店應用。

控制託管代碼優化的能力目前很有限。C# 和 Visual Basic 編譯器僅可以使用 /optimize 開關打開或關閉優化。若要控制 JIT 優化,可以將 System.Runtime.Compiler­Services.MethodImpl 屬性應用於具有某個選項(來自指定的 MethodImplOptions)的方法。NoOptimization 選項可關閉優化,NoInlining 選項可防止內聯此方法,AggressiveInlining (.NET 4.5) 選項可以爲 JIT 編譯器提供內聯方法建議(不僅僅提供提示)。

總結

本文討論的所有優化技術均可以按以兩位數的百分比顯著提高代碼性能,並且 Visual C++ 編譯器支持所有這些優化技術。這些技術的重要之處在於,應用這些技術可以使編譯器能夠執行其他優化。本文絕不是關於 Visual C++ 所執行的編譯器優化的全面討論。但是,我希望通過本文您已領略到編譯器的強大功能。Visual C++ 可以做的遠不止這些,因此請繼續關注第 2 部分。


Hadi Brais 是印度新德里理工大學 (IITD) 博士,主要研究針對下一代內存技術的編譯器優化。他將大部分精力用在編寫 C/C++/C# 代碼方面,並深入研究了 CLR 和 CRT。他的博客網址是hadibrais.wordpress.com。您可以通過 [email protected] 與他聯繫。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章