0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

.NET8性能优化之线程

OSC开源社区 来源:江湖评谈 2024-01-22 14:50 次阅读

前言

首先来看下,为什么性能会一直持续性优化。.NET8引入的SSE-XMM(16字节)Register和AVX-YMM(32字节)Register是关键,传统的Register一般指令集层次能移动的最多只有8位,就算是最新的x64系统。但是SSE和AVX改变了这种局面,它们能一次性移动64位系统的一倍乃至四倍,这就是优化的关键。

之前的多篇文章展示了很多.NET8的性能优化,基本上都是核心级的CLR/JIT优化,包括了VM,Zeroing,CHRL,Exception,Non_GC,Branch,GC,Reflection,AOT,Enum,DateTime等等。但是漏掉了一个较为重要的东西:线程。本篇来看下.NET8里面的线程优化。

ThreadStatic

.NET在新的版本中,对线程,并发,并行,异步等方面做出了非常大的改进。比如ThreadPool完全重写,异步方法基础部分的完全重写,ConcurrentQueue队列的完全重写等等。.NET8在这些的基础上,进行了更为深思熟虑的和更为有影响力的改进。比如ThreadStatic。

.NET运行时里面运用本地数据和线程的关联,就是本地线程存储(TLS)。在托管代码上实现这一点,最常用的方法就是用[ThreadStatic]属性注解一个静态字段(当然这里还有个用途更高级的ThreadLocal),这样就会导致.NET运行时会把这个静态字段的存储复制到每个线程,而不是全局的进程上面。

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

private static int s_onePerProcess;


[ThreadStatic]
private static int t_onePerThread;

在.NET8之前访问被TheadStatic标记的字段,需要一个JIT的非内联辅助方法CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_NOCTOR。它的原型实际上就是JIT_GetSharedNonGCThreadStaticBase。如下:

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

因为这个方法本身是有优化空间的,经过dotnet/runtime#82973 and dotnet/runtime#85619它的函数本体被内联到了调用者当中了。省略了函数调用以及跳转的成本。通过一个基准测试来看下这个效果。

// 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;
}

测试结果如下,提升明显:

方法 运行时 平均值 比率
Increment .NET 7.0 8.492 ns 1.00
Increment .NET 8.0 1.453 ns 0.17

同样的通过

dotnet/runtime#84566 和 dotnet/runtime#87148为.NET AOT做的一个优化,提升同样明显。

方法 运行时 平均值 比率
Increment NativeAOT 7.0 2.305 ns 1.00
Increment NativeAOT 8.0 1.325 ns 0.57

ThreadPool

TheadPool优化在于线程池方面,之前老版本的.NET基本上都是通过封装Windows线程池,然后通过托管代码调用。但是在.NET6里面开始.NET运行时实现了自己的托管线程池,也就是说新版的.NET包含了两个线程池。分别为托管调用的windows线程池,以及托管代码自己实现的托管线程池。现在,在.NET8里面可以自由切换这两个线程池,你想使用哪个就用哪个,以提升程序的性能。

我们来看下,这个过程。首先新建一个.NET8.0控制台应用程序,代码如下

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

并在 .csproj 中添加true。先运行下它,结果显示如下:

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这个就是.NET6以来新增的托管线程池操控的代码。我们下面再来看下Windows线程池方面,把上面代码进行AOT编译

dotnet publish -c Release -r win-x64

我们运行下路径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线程池调用),而上面的则是PortableThreadPool(.NET运行时自己实现的托管线程池)。这里有个疑问,为什么AOT可以看到Windows线程池,因为AOT是本地预编译机器码,它不包含托管代码,所以只能Windows自带线程池调用。但是如果是托管代码,不是AOT化,那么可以看到原汁原味的托管线程池调用。

通过issuse:dotnet/runtime#85373,Windows上运行的.NET8应用程序可以选择任何一个线程池。

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

false

false表示不使用Windows线程池,True表示使用。其它的,也可以设置环境变量,来使用Windows线程池,设置0则不使用。

DOTNET_ThreadPool_UseWindowsThreadPool=1

目前来说,没有确切的证据证明哪个线程池好用,或者效率更高。但是开发者可以使用上面的选项来进行自己的选择,有一个测试就是在Windows线程池在比较大的机器上的IO扩展性不太好。如果你的应用程序已经大量的使用了Windows线程池,那么可以通过以上设置为另一个线程池操作也是可以的。此外,线程池经常被阻塞,Windows线程池对此有更多的处理,也能更有效的比托管线程处理的更好。如以下代码:

// 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}");

以上创建了很多工作项,所有的工作项都会被阻塞,直到所有工作项都被处理完毕。这里可以设置DOTNET_ThreadPool_UseWindowsThreadPool 为 1。看下对比的结果,显示Windows线程池处理的更好。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68521
  • 指令集
    +关注

    关注

    0

    文章

    222

    浏览量

    23378
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19675

原文标题:.NET8极致性能优化-线程

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    css的性能优化重点

    网站前端性能优化javascript和css
    发表于 10-21 09:12

    MySQL优化查询性能优化查询优化器的局限性与提示

    MySQL优化三:查询性能优化查询优化器的局限性与提示
    发表于 06-02 06:34

    线程管理线程切换

    线程管理线程切换前言基本信息前言说明PendSV_Handler函数前言基本信息名称描述说明RT-Thread Studio 软件版本版本: 1.1.3RT-Thread 系统版本
    发表于 08-24 08:19

    《现代CPU性能分析与优化》---精简的优化

    来提高程序的性能。由于这本书是Denis在easyperf.net博客分享内容的系统整理和总结。更加偏向实战类型,在阅读过程中,可以直接对自己的C/C++代码进行剖析和优化实验。当然这本书也是有缺点
    发表于 04-18 16:03

    Oracle数据库网络安全访问机制

    本文主要分析了Oracle 客户端通过Net8 访问数据库服务器过程,阐述了Oracle 数据库的网络访问机制以及Net8 在实现Oracle 数据库的服务器和客户端之间安全的数据通信中的重要作用。
    发表于 08-29 10:20 20次下载

    利用缓存技术优化基于ASP.NET的Web GIS性能

    随着Web GIS的快速发展和广泛应用,对Web GIS的性能提出了更高的要求。ASP.NET是微软推出的新一代动态网页技术,它提供了强大的Web应用开发功能,依托ActiveX技术,开发基于ASP.NET的Web
    发表于 09-23 10:54 17次下载

    基于_NET线程间通讯技术的应用_张雪飞

    基于_NET线程间通讯技术的应用_张雪飞
    发表于 03-19 11:31 0次下载

    C#多线程技术

    C#和.NET类库为开发多线程应用程序提供了很方便的支持,本章首先简要介绍.NET类库中的Thread类及各种线程支持,再通过示例说明线程使
    发表于 04-23 11:32 15次下载

    LibTorch-based推理引擎优化内存使用和线程

    LibTorch-based推理引擎优化内存使用和线程
    的头像 发表于 08-31 14:27 1231次阅读
    LibTorch-based推理引擎<b class='flag-5'>优化</b>内存使用和<b class='flag-5'>线程</b>池

    .NET 8发布首个RC,比.NET 7的超级快更快!

    此外,RC1 在 .NET MAUI 方面带来了诸多质量改进,修复内存泄露和诸多特定平台的问题,改进了 UI 控制并优化性能,在 Mac 上支持苹果 Xcode 15。
    的头像 发表于 09-18 16:54 1376次阅读
    .<b class='flag-5'>NET</b> <b class='flag-5'>8</b>发布首个RC,比.<b class='flag-5'>NET</b> 7的超级快更快!

    .NET8为原生AOT改进 Linux上原生AOT应用程序大小最多减少50%

    ,它不需要运行时,所有内容都包含在一个文件中。 微软介绍道,.NET 8 为原生 AOT 发布带来了以下改进: 增加对 x64 和 macOS Arm64 架构的支持 将 Linux 上原生 AOT
    的头像 发表于 11-14 11:53 1435次阅读
    .<b class='flag-5'>NET8</b>为原生AOT改进 Linux上原生AOT应用程序大小最多减少50%

    .NET8为什么要引入Non-GC Heap这种机制呢?

    .NET8里面JIT引入了一个新的机制,叫做Non-GC Heap。JIT可以确保相关对象分配在Non-GC Heap上,该堆像其名称一样,不受GC管理。
    的头像 发表于 11-28 10:38 660次阅读

    .NET8极致性能优化AOT

    .NET8对于性能优化是方方面面的,所以AOT预编译机器码也是不例外的。本篇来看下对于AOT的优化
    的头像 发表于 12-06 10:16 935次阅读

    OPCUA产品情报:.NET SDK最新版本公布,系列产品稳步更新中!

    近期,Unified Automation公司推出了.NET based OPC UA SDK v4.0.0这一产品。该版本除了例行的Bug修复外,还进行了部分函数API的修改与功能的扩展,新添了对使用MQTT/JSON传输的PubSub模块和.NET8的支持。
    的头像 发表于 03-14 10:00 897次阅读
    OPCUA产品情报:.<b class='flag-5'>NET</b> SDK最新版本公布,系列产品稳步更新中!

    Linux性能优化

    一、优化内核相关参数 配置文件/etc/sysctl.conf 配置方法直接将参数添加进文件每条一行 sysctl -a 可以查看默认配置sysctl -p 执行并检测是否有错误 1、网络相关
    的头像 发表于 12-06 10:15 190次阅读
    Linux<b class='flag-5'>之</b><b class='flag-5'>性能</b><b class='flag-5'>优化</b>