C/C++ 開發人員採用 Windows 64 位

5. 針對 C/C++ 開發人員採用 Windows 64 位

 

發佈日期: 2006-7-10 | 更新日期: 2006-7-10

5.1 開發環境

目前,Windows 64 位的開發環境由兩部分組成:

在 32 位機器上開發

在 64 位機器上部署和調試

這意味着,開發週期涉及到 32 位和 64 位平臺的使用。開發週期內的代碼生成部分在 32 位平臺上執行,而部署-調試部分則在 64 位平臺上執行。

無論開發人員使用 Visual Studio 6.0 還是 Visual Studio .NET,做法都是相同的。開發人員下載並安裝 Microsoft Platform SDK,然後從預先定義的開發人員環境窗口中生成一個 Visual Studio 實例。這允許 Visual Studio 鏈接到正確的 64 位版本庫(MFC、ATL 等),並針對選定的平臺進行編譯。

5.1.1Visual Studio6.0

Visual Studio 6.0 可以與 Microsoft Platform SDK 配合使用來創建針對 IPF 平臺的 64 位應用程序,如果應用了 SDK SP1,還可以針對 x64 平臺來創建 64 位應用程序。開發代碼沒有限制;Visual Studio 的界面和功能保持相同。在較新的 Windows XP 64 位版本(build 1159,x64 版本)上,可以在 x64 機器的 WoW64 下原樣安裝並運行 Visual Studio 6。這意味着,開發人員可以在單個 x64 機器上生成、部署和調試。

5.1.2Visual Studio.NET2002 & 2003

Visual Studio .NET 2002 和 2003 可用於編譯 64 位 C/C++ 代碼。這兩個版本都可以使用 Platform SDK 生成 x64 或 IPF 二進制文件。通過 Windows Server 2003 SP1 版本,開發人員能夠使用 Visual Studio .NET 2003 在 x64 硬件上開發 x64 代碼。該功能綁定到 Windows Server 2003 中,因爲 Windows XP 64 位是根據 Windows Server 2003 代碼基構建的,因此每個版本的 Service Pack 都是同步的。這讓開發人員不再使用雙平臺方法來進行 Windows 64 位應用程序的開發。但是,開發人員仍需要單獨開發和調試應用程序,因爲 32 位 Visual Studio 不能調試 64 位代碼。

5.1.3Visual Studio.NET2005

在 Visual Studio .NET 的下一個版本(名爲 Visual Studio .NET 2005)中,開發人員將能夠針對 64 位進行開發、部署和調試。但是,Visual Studio .NET 2005 仍然在 WoW64 模式下運行。因此,調試將遠程完成(即使它能夠在調試 64 位代碼的機器上運行)。Visual Studio .NET 2005 允許開發人員針對其安裝的模塊和擴展所支持的任何 Windows 平臺進行交叉編譯。開發人員可以針對 32 位 Windows 以及 64 位 Windows 來構建應用程序,還可以針對移動平臺來構建應用程序。


 

5.2 調試

C/C++ 開發人員有兩個用於調試 64 位代碼的選項:標準 Visual Studio 調試器或 WinDGB。標準 Visual Studio 調試器應該能夠滿足大多數需要。但是,如果開發人員需要調試內核模式代碼、託管代碼,或者執行較爲複雜的任務,則 WinDBG 是較合適的調試器。

5.2.1Visual Studio

Visual Studio .NET 2003 中的 Visual Studio 調試器已經遷移到 64 位 IPF。它具有並提供與 32 位版本完全相同的功能。使用該調試器的好處是,大多數開發人員都很熟悉該工具,並且它是一個基於 GUI、非常直觀的調試器。但它只能執行數量有限的低級別調試。

遷移後的調試器可以通過平臺 SDK 使用,並且需要在本地 IPF 機器上(沒有遠程功能)運行。64 位版本的 Visual Studio .NET 調試器不能調試 WoW64 模式下運行的 32 位代碼,也不能從運行的進程中連接和分離。

在 Visual Studio 2005 版本中,由於調試能夠從構建環境中完成,因此該過程被大大簡化了。Visual Studio .NET 2005 還具有類似於 WinDBG 的低級別調試的功能和特性。

5.2.2WinDBG

正如以下屏幕截圖所示,WinDBG 是一個低級別調試工具。它具有一個 GUI,但不是直觀的 Visual Studio GUI。這是一個強大的工具,它需要您首先花些精力來熟悉它的操作和功能。它有一個基本的 GUI,開發人員可以在其中使用鼠標、傳統的點擊界面和多個窗口。


 

由於 WinDBG 是一個低級別工具,因此它比 Windows 的其他調試器更加靈活。它允許開發人員追蹤其部署的每種類型的代碼,並且是具有這一功能的唯一一種調試器,這些類型包括:

64 位代碼

在 WoW64 下運行的 32 位代碼

調試 WoW64 引擎

託管代碼

Windows 服務

內核模式驅動程序和代碼

WinDBG 的最後一個版本允許開發人員隨意地與正在運行的進程連接或分離,而不會影響進程本身。這項操作還可以遠程完成。

5.3 編碼問題

將 C/C++ 應用程序遷移到 64 位時,遇到的大部分編碼問題可分爲三類:指針轉換、指針運算和對齊。在處理內聯彙編程序,使用修改後的五個 API 調用之一,以及嘗試跨 32/64 位邊界通信時,可能會出現其他問題。

由於數據類型 Int 和 Long 的長度保持不變(仍爲 32 位),因此需要修改的代碼數量非常少。通常,所涉及的代碼行數應該不到總代碼基的 1%。這與 Unix 不同,其中 Long 要遷移到 64 位。

開發人員必須小心處理變量的對齊。未對齊對性能的影響非常嚴重。對於 x64,有一些性能影響,但是在 Itanium 系統上,問題更嚴重;異常將傳播到應用程序層並會導致應用程序崩潰。

開發人員可以使用 –Wp64 編譯器開關來要求編譯器顯示可能的移植性問題。這將使開發人員能夠注意到絕大多數的移植問題。該標誌在 32 位模式下同樣可用。

5.3.1類型大小

從 32 位遷移到 64 位時,增長的主要類型是指針和派生數據類型,如句柄。在 Windows 64 位中,目前的指針和派生類型是 64 位 long 類型。大小增加的其他一些類型還有:WPARAM、LPARAM、LRESULT 和 SIZE_T。其中一個原因是,它們作爲參數使用,並且某些函數將指針作爲參數使用。

從“int”和“long”派生出的所有類型的大小仍然是 32 位,其中包括 DWORD、UINT 和 ULONG。小於 32 位的類型保留它們當前的大小。一個示例就是“short”數據類型,它仍然保留爲 16 位的帶符號整數。

正如前面提到的那樣,Win32 API 保持不變。所進行的更改對應於五個替代函數;其中,四個由一個多態版本取代,一個用於平面滾動條:

GetClassLongPtr()

GetWindowLongPtr()

SetClassLongPtr()

SetWindowLongPtr()

這些函數的名稱已經更改。此外,這些函數已經調整爲使用多態數據類型(如 UINT_PTR),並使用所有更新的常量。

由於對 Win32 API 的更改極少(現在只稱爲“Windows API”),Win32 API 專家基本上都是 64 位 API 專家。開發人員在 Win32 技能和代碼上的投入並沒有損失。當然,也有少量的更改,但開發方式保持不變。

開發人員針對單個代碼基的最佳做法是,同時針對 32 位和 64 位進行編譯。這使得開發人員能夠確保他們在 32 位代碼和技能上的投入沒有損失。他們不應該爲了計算和操作的目的,而編寫依賴或假定數據類型大小的代碼。這些代碼很可能無法移植,或者在移植過程中製造困難。開發人員編寫的代碼是否乾淨,一個明顯的標誌是:當 W4 級警告開啓時,他們是否能夠順利地編譯這些代碼。這並不特別針對 64 位問題,許多可移植性問題都通過這個方法識別。通常,這些代碼都會針對兩個平臺編譯,並立即運行。

以下示例說明,截至目前所提到的類型大小問題。第一個示例中的指針混合使用了“int”或“long”類型。


 

在該示例中,x 是一個整數,它被賦予地址 y。這可以在 32 位上執行,因爲整數和指針的大小相同,所以沒有數據丟失。但在 64 位中,整數長度仍爲 32 位,但指針長度是 64 位。因此,儘管在理論上該示例可以執行該賦值,但截斷值會導致數據丟失。


 

第二個示例演示 int 類型在指針運算中的錯誤使用。將 charArray 指針轉換爲 int,以便計算 char 數組中的偏移值,然後再將結果轉換回指針。由於數據類型的大小不同,因此會發生丟失數據的情況,內存故障也是不可避免的。


 

最後一個示例演示在應當使用 handle 類型的位置錯誤地使用了 LONG 類型。在 32 位環境中,將 HANDLE 轉換爲 LONG 始終是完全合法的,但是它不可移植。這是指針與數據類型大小不匹配導致在 64 位環境下出現問題的另一個示例。

即使指針大小加倍,由於新的多態類型,它仍然可以是透明的。它們在 32 位和 64 位中同等地表示指針。如果使用適當,它們可根據相同的源代碼基在 32 位和 64 位上正確地編譯和運行。如果使用不正確,指針將被截斷並可能導致難以解決的錯誤。

5.3.2對齊問題

移植問題的另一個常見來源是數據結構對齊。數據類型傾向於根據數據類型本身的大小按邊界對齊。例如,字符按 1 字節邊界對齊,而整數按 4 字節邊界對齊。

下圖中的結構闡釋了這個問題。“a”字段(是一個字符)在結構開頭處正確對齊。但是,“b”和“c”整數字段在結構中的下一個可用 4 字節邊界上對齊。這就在“a”和“b”之間強制使用了 3 字節填充,以便符合整數所需的 4 字節對齊。


 

同樣,“d”(是一個指針)將在下一個可用的 8 字節邊界上對齊。由於“a”、“b”、“c”以及“a”和“b”之間的填充總大小爲 12 字節,因此需要在“c”之後添加 4 個額外的填充字節,以便“d”可以在 8 字節邊界上正確對齊。

如果在運行時對結構在內存中的實現方式進行不正確的假設,那麼這些結構分配上的更改會導致許多問題。

例如,假設結構中“d”的偏移值距離結構開頭處 12 字節,如果嘗試進行基於偏移值的直接賦值或訪問,就會出現問題。目前就有現成的機制允許用戶以安全且與平臺無關的方式使用基於偏移值的訪問。

下圖說明結構在 32 位平臺和 64 位平臺上填充方式之間的差別。開發人員應該瞭解體系結構的填充規則,並在自然邊界上理想地對齊所有結構成員。

下面的結構在各個平臺上大不相同,因此對象跨 32/64 位邊界的任何傳輸都會產生問題。


 

聯合是可移植性缺陷的另一個來源。如果其中一個成員是指針或者其大小在 64 位中增加的任何數據類型,則聯合的大小會增加。


 

聯合在封閉結構內甚至內存中的對齊方式將由聯合的第一個成員的大小決定。因此,如果第一個成員是指針,聯合將對齊到 8 字節邊界;如果是 long 類型(如示例所示),聯合將對齊到 4 字節邊界。

這些類型的更改會導致內存分配問題、對齊問題以及無效的偏移值運算。堆棧對齊可能也是問題的一個來源,因爲在 Windows 32 位中,堆棧始終在 4 字節邊界上對齊,而 Windows 64 位始終在 8 字節邊界上對齊堆棧。

以下是數據在堆棧和指針賦值中的未對齊示例。對指針變量的賦值使用的內存地址不匹配 8 字節邊界,因爲它是從“temp”數組的第二個字符開始的。


 

在最佳情況下,諸如此類的未對齊會影響性能。代碼的速度以及可執行文件的大小都會受到負面影響。這是因爲編譯器將添加額外的代碼來修正未對齊,因而對取消引用的內存空間執行簡單的讀寫操作都需要“先對齊後操作”這個過程。

但是,在最糟糕的情況下,會導致處理器異常和應用程序崩潰。這對 IPF 而言更是如此。以前的 x86 體系結構以無提示的方式處理這個問題,並且在遇到未對齊時不會引發異常。因爲許多應用程序中沒有處理這些異常的代碼,所以,如果應用程序有這方面的缺陷,則很可能會失敗。

違反對齊原則的結果視平臺而定。包括以下情況:

x86 — 引發一個異常,但操作系統會即時修正未對齊。

IPF其行爲方式類似於 x86,但操作系統不修正錯誤。

x64 — 硬件不引發異常;修正在硬件級別完成。

避免未對齊的方式有多種。其中一個是使用 __unaligned 關鍵字。它允許訪問未對齊的數據;但是,即使數據對齊正確,應用程序也要付出性能代價。不推薦您使用該方法,除非其他選項均不可行。

__unaligned 關鍵字會導致編譯器插入代碼,以即時修正未對齊問題。這將增加可執行文件的整體大小,並且是性能損失的來源。

還可以使用 __declspec(align()) 指示數據應該在特定邊界上對齊。此外,_aligned_malloc() 調用允許開發人員以預先對齊的方式分配內存。這是推薦的最佳做法,以確保所有數據按自然邊界對齊。由於面向性能的原因,大多數應用程序供應商都遷移到 64 位平臺,因此,開發人員關注對齊對於防止應用程序降級至關重要。

5.4 性能分析

性能調整應該被視爲遷移到 Windows 64 位的必要階段。由於採用 64 位的主要驅動力是性能因素,因此最佳化應用程序將產生重大的性能收益。該階段在 Windows 64 位中十分重要,因爲處理器和編譯器技術已經發展得很完美並已經具有許多領先技術,所以只有採用正確的調整方法後才能利用它們。

這個過程可以分爲三個階段:第一個階段,使用標準的獨立於平臺的編譯器優化(例如,大小與速度),由開發人員經驗驅動的基本增強功能。第二個階段,使用芯片廠商提供的優化工具:VTune(用於 Intel)和 CodeAnalyst(用於 AMD)。這些特定於芯片的工具應該充分利用這些體系結構所提供的最新技術的優勢。最後一個階段,使用配置文件導引優化 (POGO),以利用實際數據作爲指導來提高應用程序的性能。

這些階段之間互補並且不會相互競爭。要在應用程序中獲得儘可能高的性能,開發人員應該使用所有可用的工具,並確定哪些優化對其解決方案最有意義。

5.4.1編譯器優化

標準 Platform SDK 已經對代碼執行了衆所周知的 x86 優化,例如,循環開解、內聯等。如果開發人員不打算使用特定於處理器的優化或 POGO,至少應該執行這個階段。

即使開發人員打算使用更完善的優化選項,但對於應該在何時、何地執行標準編譯器選項而言,這仍然是一個進行基本分析的好方法。

5.4.2 AMDCodeAnalyst

AMD CodeAnalyst 性能分析器是一組功能強大的工具,用於在 AMD 微處理器上分析軟件性能。這些工具旨在支持 x86 和 AMD64 體系結構上的 Windows 2000 和 Microsoft Windows XP 版本。

雖然大多數用戶會選擇 GUI,但還是要提供配置文件作爲一個命令行實用工具,以加強它在批處理文件中的使用。在 AMD CodeAnalyst 性能分析器包含了 AMD Geode 處理器的“基於計時器的分析”功能。

分析在優化的第一個階段內使用。AMD CodeAnalyst 性能分析器提供“基於計時器的分析”和“基於事件的分析”選項。

在“基於計時器的分析”中,要優化的應用程序在運行 AMD CodeAnalyst 性能分析器的機器上全速運行。EIP 採樣按預定的時間間隔收集,並可以用來識別可能的瓶頸、執行損失或優化機會。

基於事件的分析使用處理器中的性能計數器來計算特定處理器事件的發生次數。當達到事件的指定計數器閾值時,抽樣驅動程序會從處理器收集 EIP 採樣。一個會話中最多可以分析四個處理器事件。

“基於計時器的分析”和“基於事件的分析”可以從多處理器系統的多個處理器中收集數據。

管道模擬在優化的第二個階段內使用,以查找導致瓶頸的原因。在模擬期間,系統將首先跟蹤應用程序執行,然後在選定的目標處理器上模擬。每條指令執行的詳細數據都會考慮前面執行的指令以及處理器緩存的狀態。模擬僅支持單個處理器執行。

“管道模擬”支持 32 位代碼的模擬,並提供以下選擇:AMD Athlon、Athlon XP、Opteron 和 Athlon64 處理器。“管道模擬”還支持 64 位代碼的模擬,並提供 Opteron 和 Athlon64 處理器選擇。

CodeAnalyst2.2功能概述

系統範圍分析:CodeAnalyst 2.2 旨在分析二進制模塊的性能,包括用戶模式應用程序模塊和內核模式驅動程序模塊。

基於計時器的分析:在啓用 APIC 的系統上,最高的時間精度是 0.1ms;在未啓用 APIC 的系統上,時間精度是 1.0ms。

多處理器分析:CodeAnalyst 2.2 可以在多處理器系統上分析(基於計時器的分析和基於事件的分析均如此)— 最多是 8 CPU 系統。

基於事件的分析:CodeAnalyst 2.2 中基於事件的分析,旨在分析 AMD Athlon 和 AMD Athlon XP 的 32 個公共性能事件,以及 AMD Opteron 和 AMD Athlon 64 上的全部 78 個性能事件和事件組合。CodeAnalyst 中基於事件的分析,旨在同時分析最多四個事件。

執行管道模擬:CodeAnalyst 2.2 包括模擬會話的配置和執行,以及通過圖形用戶界面的基於計時器和基於事件的會話。

後處理:CodeAnalyst 2.2 無需模塊調試信息即可顯示採樣分配。

5.4.3 Intel VTune

Intel VTune 性能分析器是一個有助於查找和移除性能瓶頸的工具。查找瓶頸很重要 — 只需花費少量的精力即可使應用程序的速度顯著提升。難點在於查找製造瓶頸的問題。VTune 簡化了這個任務並提供了查找問題的答案。它通過三個機制或技術來完成這個任務:

採樣技術使用處理器中斷來識別性能瓶頸,忽略系統開銷。其他性能分析器(OptimizeIt、Quantify 等)不使用硬件中斷,因此它們會大大降低所測試應用程序的速度。速度降低會導致應用程序無法按正常方式執行,並會導致“誤檢”。換句話說,就是分析器無法正確識別瓶頸。但 VTune 對硬件中斷的巧妙使用降低了系統開銷,從而使應用程序能夠按正常方式執行,並識別真正的瓶頸。該功能還允許開發人員深入源代碼,以查看特定瓶頸的位置並瞭解需要修正的內容。

調用圖技術使應用程序二進制能夠追蹤程序的流控制。所收集的信息通過易於使用的 GUI 呈現給用戶。用戶可以識別關鍵路徑(即,程序中的最長路徑),查看每個函數所花費的時間,並瞭解他們需要在哪裏花費精力來優化代碼。

計數器監視功能使用戶能夠在運行時跟蹤系統活動,並識別系統中的瓶頸(是內存不足,還是 I/O 性能問題,或是緩存問題)。然後,用戶可以調用 Intel Tuning Assistant 來進一步瞭解性能問題和調整建議。

該版本中的一個新功能是按時間採樣 (Sampling Over Time)。它根據時間呈現性能和處理器事件的歷史。通過這個功能,用戶能夠查看系統行爲和應用程序性能如何隨着時間而改變。查看這項內容很重要,例如,可以瞭解特定的事件或應用程序行爲模式如何影響性能。例如,如果用戶希望瞭解特定應用程序事件(如數據庫查詢,或動作遊戲中的爆炸)如何影響性能,則可以查看在事件發生時性能如何改變。該功能還可以幫助識別相關的性能數據,並過濾掉對解決問題無用的數據。

另一個新功能 — 選擇性校準 (Selective Calibration),改進了以前版本中的校準功能。VTune 將運行一個額外的初步採樣,以便爲與 CPU 時鐘無關的事件(即,除了 Clock ticks 和 Instructions Retired 以外的所有事件)決定適當的採樣率。在以前的版本中,默認情況下,始終會爲所有事件運行額外的校準,即使是那些能夠根據 CPU 的時鐘速度輕鬆並準確地計算出採樣率的事件(Clock ticks 和 Instructions Retired 都帶有這個額外的運行)。首次觸及該功能的用戶總是很困惑 — 應用程序爲什麼要執行兩次?現在,默認事件(Clock ticks 和 Instructions Retired)就不會導致應用程序再運行兩次了。

VTune Analyzer 7.1 還支持高性能計算 (HPC),機器最多使用 64 個處理器。

5.4.4 POGO

POGO 是 Windows 64 位 Platform SDK 附帶的一個新工具。它是一個新的優化方法,涉及到監視應用程序的常用執行路徑,以及從該信息獲得潛在的優化。

POGO 的思路很簡單:創建一個程序,收集有關其常見用法的統計信息,並對其進行優化,使其能夠儘可能高效地執行這些任務。創建 POGO 優化的應用程序步驟如下:

將源代碼編譯並鏈接到測試代碼。

在常見用法或開發人員希望爲其生成優化的用法中體驗測試代碼。

重新編譯源代碼(包括在前面步驟中收集的分析信息),以便 POGO 能夠生成優化的可執行文件。

以下是配置文件導引優化執行的一些較常見任務:

內聯:POGO 爲內聯代碼決定最佳時機。

功能佈局:磁盤訪問開銷很大;該優化可以將最常用的功能放在一起。

大小/速度優化:調用較頻繁的功能針對速度進行優化,調用不太頻繁的功能針對大小進行優化。

塊佈局:優化最熱門的路徑,使不常用的分支需要較長的跳躍。它降低了分支的成本並增加緩存的利用率。

分離“冷”代碼:將分析過程中根本不調用的代碼移到末尾。因此,工作集中的頁面只包含經常調用的代碼。

5.6 小結和建議

上述討論的要點包括以下幾項:

即使開發人員不打算立即移植到 64 位,他們也應該儘早在 Wow64 模式下測試代碼。

開發人員應該始終採用單個源代碼基,並使他們生成的版本是針對每個所需的平臺(x86、IPF 和 x64)。

開發人員不應該假設數據大小,並且應該謹慎訪問自然邊界上的數據。

開發人員應該將優化的最後一個階段視爲必要階段,並使用所有可用的工具充分利用他們的應用程序。

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