.NET中测量多线程基准性能
多线程基准性能是用来衡量计算机系统或应用程序在多线程环境下的执行能力和性能的度量指标。它通常用来评估系统在并行处理任务时的效率和性能。测量中通常创建多个线程并在这些线程上执行并发任务,以模拟实际应用程序的并行处理需求。
在此,我们用多个线程来完成一个计数任务,简单地测量系统的多线程基准性能,以下的5种测量代码(代码1,代码4,代码5,代码6,代码7)中,都设置了计数器,每一秒计数器的计数量体现了系统的性能。通过对比这些测量方法,可以直观地理解多线程、如何通过多线程充分利用系统性能,以及运行多线程可能存在的瓶颈。
测量方法
先用一个多线程的共享变量自增例子来做多线程基准性能测量:
//代码1:简单的多线程测量多线程基准性能
long totalCount = 0;
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
tasks[i] = Task.Run(DoWork);
}
while (true)
{
long t = totalCount;
Thread.Sleep(1000);
Console.WriteLine($"{totalCount - t:N0}");
}
void DoWork()
{
while (true)
{
totalCount++;
}
}
//结果
48,493,031
48,572,321
47,788,843
48,128,734
50,461,679
……
因为在多线程环境中,线程之间的切换会导致一些开销,例如保存和恢复线程上下文的时间。如果上下文切换频繁发生,可能会对性能测试结果产生影响,因此上面的代码根据系统的CPU内核数设定启动测试线程的线程数量,这些线程对一个共享的变量进行自增操作。
有多线程编程经验的人不难看出,上面的代码没有正确地保护共享资源,会出现竞态条件。这可能导致数据不一致,操作顺序不确定,或者无法重现一致的性能结果。我们将用代码展示这种情况。
//代码2:展示出竞态条件的代码
long totalCount = 0;
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
tasks[i] = Task.Run(DoWork);
}
void DoWork()
{
while (true)
{
totalCount++;
Console.Write($"{totalCount}"+",");
}
}
//结果
1,9,10,3,12,13,4,14,15,16……270035,269913,270037,270038,270036,270040,269987,270042,270043……
从代码2的运行结果可以看到,由于被不同线程操作,这些线程同时访问和修改totalCount的值,打印出来的totalCount不是顺序递增的。
可见,代码1没有线程同步机制,我们不能准确测量多线程基准性能。
C#中线程的同步方式,比如传统的锁机制(如lock语句、Monitor类、Mutex类、Semaphore类等)通常使用互斥机制来保护共享资源,以确保同一时间只有一个线程可以访问资源,避免竞争条件。这些锁机制会在代码块被锁定期间阻塞其他线程的访问,使得同一时间只有一个线程可以执行被锁定的代码。
这里使用lock锁作为线程同步机制,修正上面的代码,对共享的变量进行保护,避免共享变量同时被多个线程修改。
//代码3:使用lock锁
long totalCount = 0;
int threadCount = Environment.ProcessorCount;
object totalCountLock = new object();
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
tasks[i] = Task.Run(DoWork);
}
void DoWork()
{
while (true)
{
lock (totalCountLock)
{
totalCount++;
Console.Write($"{totalCount}"+",");
}
}
}
//结果
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30……
这时的结果就是顺序输出。
我们用含lock的代码来测量多线程基准性能:
//代码4:运用含lock锁的代码测量多线程基准性能
long totalCount = 0;
int threadCount = Environment.ProcessorCount;
object totalCountLock = new object();
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
tasks[i] = Task.Run(DoWork);
}
while (true)
{
long t = totalCount;
Thread.Sleep(1000);
Console.WriteLine($"{totalCount - t:N0}");
}
void DoWork()
{
while (true)
{
lock (totalCountLock)
{
totalCount++;
}
}
}
//结果
16,593,517
16,694,824
16,514,421
16,517,431
16,652,867
……
保证多线程环境下线程安全性,还有一种方式是使用原子操作Interlocked。与传统的锁机制(如lock语句等)不同,Interlocked类提供了一些特殊的原子操作,如Increment、Decrement、Exchange、CompareExchange等,用于对共享变量进行原子操作。这些原子操作是直接在CPU指令级别上执行的,而不需要使用传统的阻塞和互斥机制。它通过硬件级别的操作,确保对共享变量的操作是原子性的,避免了竞争条件和数据不一致的问题。
它更适合用于特定的原子操作,而不是用作通用的线程同步机制。
//代码5:运用原子操作的代码测量多线程基准性能
long totalCount = 0;
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
tasks[i] = Task.Run(DoWork);
}
while (true)
{
long t = totalCount;
Thread.Sleep(1000);
Console.WriteLine($"{totalCount - t:N0}");
}
void DoWork()
{
while (true)
{
Interlocked.Increment(ref totalCount);
}
}
//结果
37,230,208
43,163,444
43,147,585
43,051,419
42,532,695
……
除了使用互斥锁、原子操作,我们也可以设法对多个线程进行数据隔离。ThreadLocal类提供了线程本地存储功能,用于在多线程环境下的数据隔离。每个线程都会有自己独立的数据副本,被储存在ThreadLocal实例中,每个ThreadLocal可以被对应线程访问到。
//代码6:运用含ThreadLocal的代码测量多线程基准性能
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
ThreadLocal<long> count = new ThreadLocal<long>(trackAllValues: true);
for (int i = 0; i < threadCount; ++i)
{
int threadId = i;
tasks[i] = Task.Run(() => DoWork(threadId));
}
while (true)
{
long old = count.Values.Sum();
Thread.Sleep(1000);
Console.WriteLine($"{count.Values.Sum() - old:N0}");
}
void DoWork(int threadId)
{
while (true)
{
count.Value++;
}
}
//结果
177,851,600
280,076,173
296,359,986
296,140,821
295,956,535
……
上面的代码使用了ThreadLocal类,我们也可以自定义一个类,给每个线程创建一个对象作为上下文,代码如下:
//代码7:运用含自定义上下文的代码测量多线程基准性能
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
Context[] ctxs = new Context[threadCount];
for (int i = 0; i < threadCount; ++i)
{
int threadId = i;
ctxs[i] = new Context();
tasks[i] = Task.Run(() => DoWork(threadId));
}
while (true)
{
long old = ctxs.Sum(v => v.TotalCount);
Thread.Sleep(1000);
Console.WriteLine($"{ctxs.Sum(v => v.TotalCount) - old:N0}");
}
void DoWork(int threadId)
{
while (true)
{
ctxs[threadId].TotalCount++;
}
}
class Context
{
public long TotalCount = 0;
}
//结果:
1,067,502,570
1,100,966,648
1,145,726,019
1,110,163,963
1,069,322,606
……
系统配置
| 组件 | 规格 |
|---|---|
| CPU | 11th Gen Intel(R) Core(TM) i5-11300H |
| 内存 | 16 GB DDR4 |
| 操作系统 | Microsoft Windows 10 家庭中文版 |
| 电源选项 | 已设置为高性能 |
| 软件 | LINQPad 7.8.5 Beta |
| 运行时 | .NET 7.0.10 |
测量结果
| 测量方法 | 1秒计数 | 性能百分比 |
|---|---|---|
| 未做线程同步 | 50,461,679 | 118.6% |
| lock锁 | 16,652,867 | 39.2% |
| 原子操作(Interlocked) | 42,532,695 | 100% |
| ThreadLocal | 295,956,535 | 695.8% |
| 自定义上下文(Context) | 1,069,322,606 | 2514.1% |
结果分析
未作线程同步测量到的结果是不准确的,不能作为依据。
根据程序运行的结果可以看到,使用传统的lock锁机制,效率不高。使用原子操作Interlocked,效率比传统锁要高近2倍。
而实现了线程间隔离的2种方法,效率都比前面的方法要高。使用自定义上下文的程序效率是最高的。
线程间隔离的两种代码,它们主要区别在于线程安全性的实现方式。代码6使用ThreadLocal 类来实现,而代码7使用了自定义的上下文,用一个数组来为每个线程提供一个唯一的上下文。代码6使用的是线程本地存储(Thread Local Storage,TLS)来实现其功能。它是一种全局变量,可以被正在运行的所有线程访问,但每个线程所看到的值都是私有的。虽然这个特性使ThreadLocal在多线程编程中变得非常有用,但为了实现这个特性,它在内部实现了一套复杂的机制,比如它会创建一个弱引用的哈希表来存储每个线程的数据。这个内部实现细节增加了相应的计算和访问开销。
对于代码7,它创建了一个名为Context的类数组,每个线程都有其自己的Context对象,并在执行过程中修改这个对象。由于每个线程自身管理其Context对象,不存在任何线程间冲突,这就减少了许多额外的开销。
因此,虽然代码6和代码7都实现了线程数据隔离,但代码7避开了ThreadLocal的额外开销,因此在性能上表现得更好。
结论
如果能实现线程间的隔离,可以大幅提高多线程代码效率,测量出系统的最大性能值。
作者:百宝门-后端组-董校刚
原文地址:https://blog.baibaomen.com/120-2/
.NET中测量多线程基准性能的更多相关文章
- 使用chrome开发者工具中的network面板测量网站网络性能
前面的话 Chrome 开发者工具是一套内置于Google Chrome中的Web开发和调试工具,可用来对网站进行迭代.调试和分析.使用 Network 面板测量网站网络性能.本文将详细介绍chrom ...
- [转载]ArcGIS Engine 中的多线程使用
ArcGIS Engine 中的多线程使用 原文链接 http://anshien.blog.163.com/blog/static/169966308201082441114173/ 一直都想写 ...
- OS X 和iOS 中的多线程技术(上)
OS X 和iOS 中的多线程技术(上) 本文梳理了OS X 和iOS 系统中提供的多线程技术.并且对这些技术的使用给出了一些实用的建议. 多线程的目的:通过并发执行提高 CPU 的使用效率,进而提供 ...
- 第35节:Java面向对象中的多线程
Java面向对象中的多线程 多线程 在Java面向对象中的多线程中,要理解多线程的知识点,首先要掌握什么是进程,什么是线程?为什么有多线程呢?多线程存在的意义有什么什么呢?线程的创建方式又有哪些?以及 ...
- 【worker】js中的多线程
因为下个项目中要用到一些倒计时的功能,所以就提前准备了一下,省的到时候出现一下界面不友好和一些其他的事情.正好趁着这个机会也加深一下html5中的多线程worker的用法和理解. Worker简介 J ...
- 公司HBase基准性能测试之结果篇
上一篇文章<公司HBase基准性能测试之准备篇>中详细介绍了本次性能测试的基本准备情况,包括测试集群架构.单台机器软硬件配置.测试工具以及测试方法等,在此基础上本篇文章主要介绍HBase在 ...
- C#中的多线程 - 并行编程 z
原文:http://www.albahari.com/threading/part5.aspx 专题:C#中的多线程 1并行编程Permalink 在这一部分,我们讨论 Framework 4.0 加 ...
- C#中的多线程 - 高级多线程 z
原文:http://www.albahari.com/threading/part4.aspx 专题:C#中的多线程 1非阻塞同步Permalink 之前,我们描述了即使是很简单的赋值或更新一个字段也 ...
- C#中的多线程 - 同步基础 z
原文:http://www.albahari.com/threading/part2.aspx 专题:C#中的多线程 1同步概要Permalink 在第 1 部分:基础知识中,我们描述了如何在线程上启 ...
- C#中的多线程 - 多线程的使用 z
原文:http://www.albahari.com/threading/part3.aspx 专题:C#中的多线程 1基于事件的异步模式Permalink 基于事件的异步模式(event-based ...
随机推荐
- Python基础 - 注释
单行注释 Python中使用#表示单行注释.一般在#后面添加一个空格,再添加注释内容 1 # 这是单行注释 多行注释 Python中使用三个单引号或三个双引号表示多行注释. 1 ''' 2 这是使 ...
- std::aligned_alloc
定义于头文件 <cstdlib> (c++) void * aligned_alloc ( std::size_t alignment, std::size_t size); (c++17 ...
- GPU技术在大规模数据集处理和大规模计算中的应用
目录 GPU 技术在大规模数据集处理和大规模计算中的应用 随着深度学习在人工智能领域的快速发展,大规模数据处理和大规模计算的需求日益增长.GPU(图形处理器)作为现代计算机的重要部件,被广泛应用于这些 ...
- mysql concat函数的用法
mysql中的这个函数非常强大,可以对查出的参数进行拼接,其实这个方法在java中也有api可以进行调用. 那么什么时候进行使用呢?例如,你老大叫你做一个数据库的数据采集,需要整理成文档,那么这个时候 ...
- Java反射源码学习之旅
1 背景 前段时间组内针对"拷贝实例属性是应该用BeanUtils.copyProperties()还是MapStruct"这个问题进行了一次激烈的battle.支持MapStru ...
- MIT6.s081/6.828 lectrue1:Introduction and examples
目前课程官网能够查到 2020,2021.2022 秋季的课程表,但是视频都是 2020 年录制的那一版 简单复习+回顾下自己的 OS 学习之旅 参考资料: 官网:https://pdos.csail ...
- == 与 equals 的区别?
一. 介绍: Java中的 "==" 是一个运算符,是用于比较两个对象地址值或基本数据类型之间的值是否相等.它的来源可以追溯到C语言,以及受C语言影响的许多其他编程语言. Jav ...
- Confluence 挖矿病毒 升级现有系统
Confluence 挖矿病毒 升级现有系统 背景 服务器很多服务都很卡,通过检查发现是一起运行的confluence异常,被挖矿病毒挖矿,华为云和官网也有说明. 知道问题之后,处理方式就是将现有的问 ...
- Django: 'block' tag with name 'header' appears more than once
错误原因 在同一文件中,重复引用标签多次 解决方案: 删掉重复的标签即可.
- CN2 GIA
搬瓦攻方案库存监控页面 https://stock.bwg.net/ https://bwh81.net/ https://bandwagonhost.com/ https://teddysun.c ...