0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

.NET8性能優(yōu)化之線程

OSC開(kāi)源社區(qū) ? 來(lái)源:江湖評(píng)談 ? 2024-01-22 14:50 ? 次閱讀

前言

首先來(lái)看下,為什么性能會(huì)一直持續(xù)性優(yōu)化。.NET8引入的SSE-XMM(16字節(jié))Register和AVX-YMM(32字節(jié))Register是關(guān)鍵,傳統(tǒng)的Register一般指令集層次能移動(dòng)的最多只有8位,就算是最新的x64系統(tǒng)。但是SSE和AVX改變了這種局面,它們能一次性移動(dòng)64位系統(tǒng)的一倍乃至四倍,這就是優(yōu)化的關(guān)鍵。

之前的多篇文章展示了很多.NET8的性能優(yōu)化,基本上都是核心級(jí)的CLR/JIT優(yōu)化,包括了VM,Zeroing,CHRL,Exception,Non_GC,Branch,GC,Reflection,AOT,Enum,DateTime等等。但是漏掉了一個(gè)較為重要的東西:線程。本篇來(lái)看下.NET8里面的線程優(yōu)化。

ThreadStatic

.NET在新的版本中,對(duì)線程,并發(fā),并行,異步等方面做出了非常大的改進(jìn)。比如ThreadPool完全重寫,異步方法基礎(chǔ)部分的完全重寫,ConcurrentQueue隊(duì)列的完全重寫等等。.NET8在這些的基礎(chǔ)上,進(jìn)行了更為深思熟慮的和更為有影響力的改進(jìn)。比如ThreadStatic。

.NET運(yùn)行時(shí)里面運(yùn)用本地?cái)?shù)據(jù)和線程的關(guān)聯(lián),就是本地線程存儲(chǔ)(TLS)。在托管代碼上實(shí)現(xiàn)這一點(diǎn),最常用的方法就是用[ThreadStatic]屬性注解一個(gè)靜態(tài)字段(當(dāng)然這里還有個(gè)用途更高級(jí)的ThreadLocal),這樣就會(huì)導(dǎo)致.NET運(yùn)行時(shí)會(huì)把這個(gè)靜態(tài)字段的存儲(chǔ)復(fù)制到每個(gè)線程,而不是全局的進(jìn)程上面。

例如以下ThreadStaitc屬性注解的用法

private static int s_onePerProcess;


[ThreadStatic]
private static int t_onePerThread;

在.NET8之前訪問(wèn)被TheadStatic標(biāo)記的字段,需要一個(gè)JIT的非內(nèi)聯(lián)輔助方法CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_NOCTOR。它的原型實(shí)際上就是JIT_GetSharedNonGCThreadStaticBase。如下:

#include 
HCIMPL2(void*, JIT_GetSharedNonGCThreadStaticBase, DomainLocalModule *pDomainLocalModule, DWORD dwClassDomainID)
{
//為了便于觀看,此處省略
    return HCCALL1(JIT_GetNonGCThreadStaticBase_Helper, pMT);
}
HCIMPLEND

因?yàn)檫@個(gè)方法本身是有優(yōu)化空間的,經(jīng)過(guò)dotnet/runtime#82973 and dotnet/runtime#85619它的函數(shù)本體被內(nèi)聯(lián)到了調(diào)用者當(dāng)中了。省略了函數(shù)調(diào)用以及跳轉(zhuǎn)的成本。通過(guò)一個(gè)基準(zhǔn)測(cè)試來(lái)看下這個(gè)效果。

// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
//dotnetrun-cRelease-fnet7.0--filter"*"--runtimesnativeaot7.0nativeaot8.0
using BenchmarkDotNet.Attributes;
usingBenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
    [ThreadStatic]
    private static int t_value;


    [Benchmark]
    public int Increment() => ++t_value;
}

測(cè)試結(jié)果如下,提升明顯:

方法 運(yùn)行時(shí) 平均值 比率
Increment .NET 7.0 8.492 ns 1.00
Increment .NET 8.0 1.453 ns 0.17

同樣的通過(guò)

dotnet/runtime#84566 和 dotnet/runtime#87148為.NET AOT做的一個(gè)優(yōu)化,提升同樣明顯。

方法 運(yùn)行時(shí) 平均值 比率
Increment NativeAOT 7.0 2.305 ns 1.00
Increment NativeAOT 8.0 1.325 ns 0.57

ThreadPool

TheadPool優(yōu)化在于線程池方面,之前老版本的.NET基本上都是通過(guò)封裝Windows線程池,然后通過(guò)托管代碼調(diào)用。但是在.NET6里面開(kāi)始.NET運(yùn)行時(shí)實(shí)現(xiàn)了自己的托管線程池,也就是說(shuō)新版的.NET包含了兩個(gè)線程池。分別為托管調(diào)用的windows線程池,以及托管代碼自己實(shí)現(xiàn)的托管線程池?,F(xiàn)在,在.NET8里面可以自由切換這兩個(gè)線程池,你想使用哪個(gè)就用哪個(gè),以提升程序的性能。

我們來(lái)看下,這個(gè)過(guò)程。首先新建一個(gè).NET8.0控制臺(tái)應(yīng)用程序,代碼如下

static void Main(string[] args)
{
    Task.Run(() => Console.WriteLine(Environment.StackTrace)).Wait();
Console.ReadLine();
}

并在 .csproj 中添加true。先運(yùn)行下它,結(jié)果顯示如下:

at System.Environment.get_StackTrace()
at ThreadPool_.Program.<>c.
b__0_0() in E:Visual Studio ProjectTest_ThreadPool_Program.cs:line 7 at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

PortableThreadPool這個(gè)就是.NET6以來(lái)新增的托管線程池操控的代碼。我們下面再來(lái)看下Windows線程池方面,把上面代碼進(jìn)行AOT編譯

dotnet publish -c Release -r win-x64

我們運(yùn)行下路徑inRelease et8.0win-x64publish里的exe文件,可以看到如下:

at System.Environment.get_StackTrace() + 0x21
at ThreadPool_.Program.<>c.
b__0_0() + 0x9 at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread, ExecutionContext, ContextCallback, Object) + 0x3d at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&, Thread) + 0xcc at System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x289 at System.Threading.WindowsThreadPool.DispatchCallback(IntPtr, IntPtr, IntPtr) + 0x45

很明顯的看到這里是WindowsThreadPool(Windows線程池調(diào)用),而上面的則是PortableThreadPool(.NET運(yùn)行時(shí)自己實(shí)現(xiàn)的托管線程池)。這里有個(gè)疑問(wèn),為什么AOT可以看到Windows線程池,因?yàn)锳OT是本地預(yù)編譯機(jī)器碼,它不包含托管代碼,所以只能Windows自帶線程池調(diào)用。但是如果是托管代碼,不是AOT化,那么可以看到原汁原味的托管線程池調(diào)用。

通過(guò)issuse:dotnet/runtime#85373,Windows上運(yùn)行的.NET8應(yīng)用程序可以選擇任何一個(gè)線程池。

可以在 .csproj 中的中,添加 :

false

false表示不使用Windows線程池,True表示使用。其它的,也可以設(shè)置環(huán)境變量,來(lái)使用Windows線程池,設(shè)置0則不使用。

DOTNET_ThreadPool_UseWindowsThreadPool=1

目前來(lái)說(shuō),沒(méi)有確切的證據(jù)證明哪個(gè)線程池好用,或者效率更高。但是開(kāi)發(fā)者可以使用上面的選項(xiàng)來(lái)進(jìn)行自己的選擇,有一個(gè)測(cè)試就是在Windows線程池在比較大的機(jī)器上的IO擴(kuò)展性不太好。如果你的應(yīng)用程序已經(jīng)大量的使用了Windows線程池,那么可以通過(guò)以上設(shè)置為另一個(gè)線程池操作也是可以的。此外,線程池經(jīng)常被阻塞,Windows線程池對(duì)此有更多的處理,也能更有效的比托管線程處理的更好。如以下代碼:

// dotnet run -c Release -f net8.0


usingSystem.Diagnostics;
varsw=Stopwatch.StartNew();
var barrier = new Barrier(Environment.ProcessorCount * 2 + 1);
for (int i = 0; i < barrier.ParticipantCount; i++)
{
    ThreadPool.QueueUserWorkItem(id =>
    {
        Console.WriteLine($"{sw.Elapsed}: {id}");
        barrier.SignalAndWait();
    }, i);
}


barrier.SignalAndWait();
Console.WriteLine($"Done:{sw.Elapsed}");

以上創(chuàng)建了很多工作項(xiàng),所有的工作項(xiàng)都會(huì)被阻塞,直到所有工作項(xiàng)都被處理完畢。這里可以設(shè)置DOTNET_ThreadPool_UseWindowsThreadPool 為 1??聪聦?duì)比的結(jié)果,顯示W(wǎng)indows線程池處理的更好。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4726

    瀏覽量

    68248
  • 指令集
    +關(guān)注

    關(guān)注

    0

    文章

    221

    瀏覽量

    23346
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    19636

原文標(biāo)題:.NET8極致性能優(yōu)化-線程

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    css的性能優(yōu)化重點(diǎn)

    網(wǎng)站前端性能優(yōu)化javascript和css
    發(fā)表于 10-21 09:12

    MySQL優(yōu)化查詢性能優(yōu)化查詢優(yōu)化器的局限性與提示

    MySQL優(yōu)化三:查詢性能優(yōu)化查詢優(yōu)化器的局限性與提示
    發(fā)表于 06-02 06:34

    線程管理線程切換

    線程管理線程切換前言基本信息前言說(shuō)明PendSV_Handler函數(shù)前言基本信息名稱描述說(shuō)明RT-Thread Studio 軟件版本版本: 1.1.3RT-Thread 系統(tǒng)版本
    發(fā)表于 08-24 08:19

    《現(xiàn)代CPU性能分析與優(yōu)化》---精簡(jiǎn)的優(yōu)化

    來(lái)提高程序的性能。由于這本書是Denis在easyperf.net博客分享內(nèi)容的系統(tǒng)整理和總結(jié)。更加偏向?qū)崙?zhàn)類型,在閱讀過(guò)程中,可以直接對(duì)自己的C/C++代碼進(jìn)行剖析和優(yōu)化實(shí)驗(yàn)。當(dāng)然這本書也是有缺點(diǎn)
    發(fā)表于 04-18 16:03

    Oracle數(shù)據(jù)庫(kù)網(wǎng)絡(luò)安全訪問(wèn)機(jī)制

    本文主要分析了Oracle 客戶端通過(guò)Net8 訪問(wèn)數(shù)據(jù)庫(kù)服務(wù)器過(guò)程,闡述了Oracle 數(shù)據(jù)庫(kù)的網(wǎng)絡(luò)訪問(wèn)機(jī)制以及Net8 在實(shí)現(xiàn)Oracle 數(shù)據(jù)庫(kù)的服務(wù)器和客戶端之間安全的數(shù)據(jù)通信中的重要作用。
    發(fā)表于 08-29 10:20 ?20次下載

    利用緩存技術(shù)優(yōu)化基于ASP.NET的Web GIS性能

    隨著Web GIS的快速發(fā)展和廣泛應(yīng)用,對(duì)Web GIS的性能提出了更高的要求。ASP.NET是微軟推出的新一代動(dòng)態(tài)網(wǎng)頁(yè)技術(shù),它提供了強(qiáng)大的Web應(yīng)用開(kāi)發(fā)功能,依托ActiveX技術(shù),開(kāi)發(fā)基于ASP.NET的Web
    發(fā)表于 09-23 10:54 ?17次下載

    基于_NET線程間通訊技術(shù)的應(yīng)用_張雪飛

    基于_NET線程間通訊技術(shù)的應(yīng)用_張雪飛
    發(fā)表于 03-19 11:31 ?0次下載

    WindowsCE_NET實(shí)時(shí)性能的測(cè)試與研究

    WindowsCE_NET實(shí)時(shí)性能的測(cè)試與研究
    發(fā)表于 10-25 09:26 ?4次下載
    WindowsCE_<b class='flag-5'>NET</b>實(shí)時(shí)<b class='flag-5'>性能</b>的測(cè)試與研究

    C#多線程技術(shù)

    C#和.NET類庫(kù)為開(kāi)發(fā)多線程應(yīng)用程序提供了很方便的支持,本章首先簡(jiǎn)要介紹.NET類庫(kù)中的Thread類及各種線程支持,再通過(guò)示例說(shuō)明線程使
    發(fā)表于 04-23 11:32 ?15次下載

    LibTorch-based推理引擎優(yōu)化內(nèi)存使用和線程

    LibTorch-based推理引擎優(yōu)化內(nèi)存使用和線程
    的頭像 發(fā)表于 08-31 14:27 ?1152次閱讀
    LibTorch-based推理引擎<b class='flag-5'>優(yōu)化</b>內(nèi)存使用和<b class='flag-5'>線程</b>池

    .NET 8發(fā)布首個(gè)RC,比.NET 7的超級(jí)快更快!

    此外,RC1 在 .NET MAUI 方面帶來(lái)了諸多質(zhì)量改進(jìn),修復(fù)內(nèi)存泄露和諸多特定平臺(tái)的問(wèn)題,改進(jìn)了 UI 控制并優(yōu)化性能,在 Mac 上支持蘋果 Xcode 15。
    的頭像 發(fā)表于 09-18 16:54 ?1307次閱讀
    .<b class='flag-5'>NET</b> <b class='flag-5'>8</b>發(fā)布首個(gè)RC,比.<b class='flag-5'>NET</b> 7的超級(jí)快更快!

    .NET8為原生AOT改進(jìn) Linux上原生AOT應(yīng)用程序大小最多減少50%

    ,它不需要運(yùn)行時(shí),所有內(nèi)容都包含在一個(gè)文件中。 微軟介紹道,.NET 8 為原生 AOT 發(fā)布帶來(lái)了以下改進(jìn): 增加對(duì) x64 和 macOS Arm64 架構(gòu)的支持 將 Linux 上原生 AOT
    的頭像 發(fā)表于 11-14 11:53 ?1361次閱讀
    .<b class='flag-5'>NET8</b>為原生AOT改進(jìn) Linux上原生AOT應(yīng)用程序大小最多減少50%

    .NET8為什么要引入Non-GC Heap這種機(jī)制呢?

    .NET8里面JIT引入了一個(gè)新的機(jī)制,叫做Non-GC Heap。JIT可以確保相關(guān)對(duì)象分配在Non-GC Heap上,該堆像其名稱一樣,不受GC管理。
    的頭像 發(fā)表于 11-28 10:38 ?620次閱讀

    .NET8極致性能優(yōu)化AOT

    .NET8對(duì)于性能優(yōu)化是方方面面的,所以AOT預(yù)編譯機(jī)器碼也是不例外的。本篇來(lái)看下對(duì)于AOT的優(yōu)化。
    的頭像 發(fā)表于 12-06 10:16 ?885次閱讀

    OPCUA產(chǎn)品情報(bào):.NET SDK最新版本公布,系列產(chǎn)品穩(wěn)步更新中!

    近期,Unified Automation公司推出了.NET based OPC UA SDK v4.0.0這一產(chǎn)品。該版本除了例行的Bug修復(fù)外,還進(jìn)行了部分函數(shù)API的修改與功能的擴(kuò)展,新添了對(duì)使用MQTT/JSON傳輸?shù)腜ubSub模塊和.NET8的支持。
    的頭像 發(fā)表于 03-14 10:00 ?818次閱讀
    OPCUA產(chǎn)品情報(bào):.<b class='flag-5'>NET</b> SDK最新版本公布,系列產(chǎn)品穩(wěn)步更新中!