有人(甚至還有私信的)認為我寫的東西廢話太多。仔細想想,我發現自己確實有這方面的毛病,寫起來無拘無束,最後自己寫煩了就開始湊合,錯字病句也不檢查了。所以這篇文章我打算寫的儘可能簡練些。

本文討論了如何直接通過代碼形式(而非內聯彙編之類的「邪道」)使用擴展指令集以實現硬體加速,以及如何將.NET Core源碼編譯為本地代碼。然而現實是殘酷的,這篇安利 .NET Core 和 CoreRT 的文章最好以一個完整項目的形式來呈現,所以篇幅註定不可能太短。不過沒關係,我打算只寫其中最有用的部分,然後把項目發出來。

很抱歉,以上內容也全是廢話。

環境需求

CPU

支持SSE4.2指令集的 Intel 處理器。

.NET Core 3.0 SDK

應選擇最新的 每日構建 版本。

發布頁上的版本(3.0.100-preview-009812) 不支持x64的硬體加速,運行時會報 StackOverflowException 異常。

Windows

安裝了C++的 visual studio 2017。

Linux

依賴:

sudo apt-get install libcurl4-openssl-dev zlib1g-dev libkrb5-dev

如果安裝了非3.9版的clang,編譯本地代碼時需要手動設置CppCompilerAndLinker的值:

export CppCompilerAndLinker=clang-version

macOS (10.12+)

Xcode 8 或以上。

硬體加速的實現

硬體加速的支持需要 System.Runtime.Intrinsics.X86 Namespace ,此文檔很奇葩,內容缺失,還已經過時,只能勉強作為參考。對我知道我不該廢話,但這裡還是想黑一波微軟文檔。

言歸正傳,目前這組 API 的非棄用版本存在於也只存在於 .NET Core 3.0 preview 中,沒有任何依賴包可以引用。另外,只有SDK最新的幾組每日構建支持新增的X64.XXX(詳見下文)功能,低版本下調用相關API會引發異常。

本例需要用到 Sse42 類 中的 Sse42.Crc32() 方法 ,雖然在文檔中,此方法有一個 Sse42.Crc32(UInt64, UInt64) 的重載,但在最新的API版本中,此方法放在了其他位置:

新版本在 Sse42 類中新增了一個名為 X64 的嵌套類,將原 Sse42 類 的64位版本方法移動到了這裡,並提供與 Sse42.IsSupported 屬性 功能一致的同名屬性。這種改動的好處顯而易見:可以更好地配合當前的環境編寫代碼。

代碼片段如下:

private unsafe delegate uint Crc32cFunc(byte* buffer, int count, uint hashValue, bool reversed);

private static readonly Crc32cFunc CrcFunc;

//......

CrcFunc = new Crc32cFunc((buffer, count, hashValue, reversed) =>
{
#if CON_X86 // 32位系統一次可計算8位元組 (這裡只用於Windows系統)
var puint = (uint*)buffer;
uint idata;
while (count > 3)
{
idata=*(puint++); //必須使用中間變數,不能直接解引用。
hashValue = Sse42.Crc32(hashValue, idata);
count -= 4;
}
ar pubyte = (byte*)puint;
#else // 64位系統一次可計算8位元組
var pulong = (ulong*)buffer;
ulong ldata;
while (count > 7)
{
ldata = *(pulong++);
hashValue = (uint)Sse42.X64.Crc32(hashValue, ldata);
count -= 8;
}
var pubyte = (byte*)pulong;
#endif //剩下的數據不超過8位元組,所以直接逐個計算。
byte bdata;
while (count-- > 0)
{
bdata = *(pubyte++);
hashValue = Sse42.Crc32(hashValue, bdata);
}
return reversed ? ~hashValue : hashValue;
});

以下問題已經在1月8日被 @彭飛 修復了,撒花。

注意:目前 Crc32 方法有一個BUG,就是第二個操作數不能直接用指針的解引用(如上面代碼的注釋),而必須先解引用後再用。我還沒測試過其他類似的方法,不確定是否僅限於此。

編譯為本地代碼

使用 CoreRT 可以將.NET Core代碼編譯為本地代碼,具體說明參見官方的 控制台應用示例 和 類庫示例 ,這裡只說簡單的思路:

  1. 在項目中引用 Microsoft.DotNet.ILCompiler 包。
  2. 命令行或終端中執行 dotnet publish -r <RID>指令,RID可選內容為 win-x64, linux-x64osx-x64

注意:目前 CoreRT 並不支持硬體加速功能,所以編譯後,相關類的IsSupported屬性自動會被設置為False。如果依然調用相關API,會拋出 PlatformNotSupportedException。所以目前乃至任何時候在使用硬體加速API的時候,都應當謹慎。

完整項目與構建

完整項目可以在 這裡 下載

項目提供了用於構建的腳本,但要使用腳本,必須確保 .NET Core SDK 3.0 已經安裝,否則需要修改其中dotnet工具的路徑,或手動構建項目:

託管代碼使用Visual studio 2019 preview 或通過 dotnet build命令行工具進行構建;非託管代碼的構建參見上一小節(以及兩個官方示例)。

總結

目前對於.NET Core 3.0的關注點主要集中在GUI上面,但其對於硬體加速特性的支持也不容忽視。雖然這個特性目前連個像樣的文檔也沒有,但並不妨礙我們進行簡單的嘗試,這是一個絕不遜於對GUI支持的重要特性,3.0版本的正式發布非常值得我們期待。

值得一提的是,CoreRT 對於此特性的支持工作也正在進行,一旦完成,那麼我們可以用 C# 構建支持硬體加速功能的本地應用,這同樣是一件非常值得期待的事情,至少對我而言便是如此,隨著 CoreRT的發展,本文的標題中的「或」遲早會變成「和」的… P:)


推薦閱讀:
相关文章