统一并发 II——基准测试方法

目录

文章系列

介绍

测量参数

中位数吞吐量期

顺序和并发测量

工作单元

频谱数据

数据对比

图表构建和理解

理想同步原语基准

隐藏行为:边缘情况

测试场景

目标硬件和软件

基准测试:Monitor (C# Lock)——重度争用

基准测试:监视器(C#锁)——严重的间接争用——坏邻居

概括


文章系列​​​​​​​

介绍

在关于统一并发和实现的同步原语的第一篇文章中,我跳过了所有关于基准测试方法的复杂部分,但我敢于在没有任何证据的情况下对每个同步原语进行一些基本的性能评估。现在是时候深入研究用于理解通用同步原语属性的基准测试和方法论的黑暗魔法了。我将介绍我用来理解在统一并发框架下实现或包装的同步原语的属性和行为的基准测试方法,并将在C#锁(Monitor类)上应用和研究该方法。

我们将不得不考虑不同的场景、多个数据序列相互比较的问题、数据序列的校准以确保相关比较,甚至选择数据类型。然后将大量数据放入一些合理的不言自明的图表中,这是锦上添花。

统一并发框架是在GitHubNuGet上可用的开源GreenSuperGreen中实现的(3个包,LIB,单元测试基准)。

测量参数

我们需要测量三个重要参数:

  • 从处理器时间开始的CPU负载——性能计数器
  • 我们获得锁访问的次数
  • 同步原语进入和同步原语退出之间的时间跨度

CPU负载要求性能计数器每秒测量一次。访问计数器很容易。时间跨度是有问题的。没有特殊的硬件,我们没有直接的方法来获得毫秒以下的短时间间隔的精确时间测量,一个时间源必须在单线程和多线程场景中可靠地工作,而不影响测量和微秒级的不确定性。调用系统获取时间不是免费的。显然,我们正在使用的系统不是为精确的实时测量而设计的。

我们必须放弃希望,我们可以直接测量每次访问的时间跨度。为了获得亚毫秒级的时间跨度精度,我们必须适应和测量大约十秒的大量访问,然后划分时间。我们可以获得精确的时间跨度测量吗?是的。有了足够大的访问次数,我们可以精确地测量这些时间跨度,如果我们使用中位数而不是简单的平均值,我们可以忽略操作系统线程造成的一些不规则性。当然,有一些限制,我将使用SpinLock解决这些问题。

中位数吞吐量期

根据这些测量计算的时间跨度描述了由给定同步原语保护的锁定代码的给定计算复杂度的中值吞吐量周期。中值吞吐量周期有效地描述了获得锁访问、在锁定区域中执行代码和退出锁所需的中值时间跨度。测量n线程和锁定代码的给定计算复杂度,我们可以获得信息,我们可以在特定配置下通过给定的同步原语获得多少工作和推动工作的频率。

顺序和并发测量

统一并发框架目前能够统一同步原语的锁定类型。有趣的是,这些类型的同步原语的最终目标是模仿顺序执行、单线程执行。我们可以使用顺序单线程执行测量来描述理想的同步原语并创建比较基础。

让我们假设顺序单线程情况的理想线性加速是1。对于运行n线程竞争理想同步原语的nCPU,我们可以假设,加速将是1/n。真正的同步原语会比1/n更糟糕,它们必须在CPU/缓存/内存总线上的线程和数据的通信和协调上浪费一些时间。

这些关系对于理解在顺序问题上投入过多并行不会提高性能很重要。

工作单元

工作单元与给定的硬件有关。如果在无竞争CPU上的单个线程上循环执行多次,则工作单元在执行时间方面具有合理的不确定性就足够了。在现代CPU架构上计算指令、操作以及理解同步原语之间的相对差异是毫无意义的。工作单元有一些限制,它将对堆栈上的一个整数值进行整数运算。

频谱数据

为了充分理解每个同步原语的行为,我们将根据工作单元的自旋数创建频谱数据。工作单元的给定旋转次数正在创建定义的计算复杂度,并且与特定于给定配置的中间吞吐量周期相关:同步原语、线程数和场景。我们将创建一个旋转列表。对于每个旋转次数,我们将测量多个CPU负载和吞吐量周期,我们将计算给定旋转次数的中值CPU负载和旋转次数的中值吞吐量周期。

数据对比

为了理解关系,我们将比较相同旋转次数的中位数吞吐量周期和中位数CPU负载。在相同的硬件上,我们可以比较具有相同工作单元旋转次数的配置的测量数据。这将使我们能够比较不同同步原语和/或配置之间相同旋转的中位数吞吐量周期和中位数CPU负载。

图表构建和理解

Axis-X自旋数工作单元和所有其他数据序列由这个数字驱动。

Axis-y-left 10秒长的CPU 使用率测量值的中值实线轴,始终由实线数据线表示,测量点是与数据线颜色相同的四舍五入的完全填充数据点。

Axis-y-right 是吞吐量周期的10秒长测量的中值虚线轴始终由虚线数据线表示,并在中心使用不同颜色的测量圆形数据点。

注意Axis-xAxis-y-rightlog2缩放,这有助于避免数据序列没有以足够大的步长变化的大范围区域。

要明白,任何重要的是垂直线的图表交叉上构成的中值CPU数据点,中值吞吐量时期数据点一些自旋的工作数据点的单元的实际表示10秒钟的测量 ,其中自旋的数是常数,等于穿过垂直线的旋转次数。

每个图表都由针对每个同步原语在几分钟内收集的大量数据构建而成。

理想同步原语基准

基于所描述的方法,我们可以构建理想同步原语的第一个基准图表。

1:理想同步原语,具有单线程理想吞吐周期和24个线程的调整后理想吞吐周期。

在图表1中,我们可以看到一条恒定的数据线,CPU使用率的中位数大约为4% CPU。它不是很明显,但CPU数据始终是实心数据线,圆形测量点填充与数据线相同的颜色。

理想同步原语的中值吞吐量周期创建了一条对角线,这要归功于x轴和y轴右轴的log2缩放,所有非理想同步原语的非理想吞吐量周期都有一些差异。在图1中,我们可以看到两条对角线。一个是干净的测量,另一个是针对由24个线程竞争的理想同步原语的情况进行校准的。这种校准对于所有同步原语基准都是常见的。

测量的数量是为Excel中较大的图表而设计的,几乎是其两倍。将相同的图表强制转换为640像素具有挑战性,但仍然可以识别所有部分,并且不完全恒定的数据系列不会导致任何问题。

隐藏行为:边缘情况

任何依赖于同步的多线程软件都隐藏了其行为的某些部分,这些部分仅在边缘情况下可见。软件越复杂,我们对自己软件的了解就越少,因为将软件推入所有可能的边缘情况变得越来越困难,有时甚至列出所有边缘情况的完整列表可能是一个问题。生产阶段之外的性能测试并不总是能够准确地检测到问题,因为很难随机地准备在生产阶段自然发生的所有边缘情况(峰值负载)。

我坚信这些问题的解决方案在于同步原语的详细知识,因为它们在边缘情况下驱动软件行为。如果我们从时域争用的角度考虑高并发多线程环境中的软件执行路径,我们可以识别出两种类型的争用:

  1. 直接争用——共享同步原语,多个线程申请同一个资源。
  2. 间接争用——多个不相关的线程组,每个线程在最小级别上进行竞争,每个组都有自己的同步原语,多个门。

现在有趣的部分来自这个问题,在间接争用的情况下争用的来源是什么?这些组之间不共享同步原语,它们实际上可以分成不同的进程。这里的争用源是共享CPU、内存带宽和总线,尤其是在使用原子指令获取锁(混合锁定方法)的情况下。繁忙的自旋等待也会消耗CPU周期,现代CPU的同步多线程显着减少了这一点,但无法避免内存带宽的减少,可重复获取条目的互锁操作伴随着高速缓存同步影响其他服务在共享服务器盒上。

1:直接争用

2:间接争用

上面的图片描述了一个远离边缘情况的简单基准,平均争用,同步造成的资源浪费通常很小。这是最简单的测试场景,通常不需要对测试代码进行复杂的更改,但它不能用于推断或推理边缘情况下峰值负载期间的行为,因为峰值负载期间的同步成本能够消耗99%CPU资源只需很少的工作完成,并且在简单的基准测试中,同步争用的概率大大降低,这意味着我们经常无法观察边缘情况,如果不更改代码,我们的代码中存在大量争用。简单的基准测试只能描述同步原语影响的不完整图片。在这种情况下,

3:严重的直接争用

对于重度间接争用场景,这将是类似的,它能够研究多个不相关的同步原语之间的硬件副作用,这些原语除了硬件之外不共享任何东西。

4:严重的间接争用

测试场景

将有两种类型的测试场景:

  1. 重度争用——代表重度直接争用的最难案例
  2. 坏邻居——代表重度间接竞争的最困难的情况,其中最小竞争的线程对的累积效应和与其他人一起玩的能力将尽可能可见

边缘情况下的这两种场景,在峰值负载下,可以被视为时域压缩,这将增加争用级别,因为更有可能捕获其他具有独占访问权限的线程。

目标硬件和软件

5:经过测试的硬件和软件

基准测试:Monitor (C# Lock)——重度争用

Monitor的吞吐量,C#锁,表现得非常好,单调地与顺序吞吐量类似,没有意外。但是,消耗的CPU资源量令人担忧。吞吐量越低,在严重争用场景中浪费的CPU资源就越多!这实际上很重要,因为它与常识相反!

解释很简单。如前所述,监视器、C#锁是一种混合锁。在尝试获得访问权的过程中,当其他线程已经拥有访问权时,该线程不会立即挂起,而是在等待更快地获得访问权,希望限制线程挂起/恢复成本,如果没有工作,线程被挂起。这种混合方法是在第一个多核时代引入的,当时它使吞吐量更快,但现在有24个或更多核,它带来了沉重的成本,并且每增加一个核,情况就会变得更糟。

我们在Monitor中的工作时间很短,C#锁定的场景,但是有很多请求会丢失大量CPU资源!显然,我们不能再忽视落后于硬件架构的遗留代码库和老化的软件架构。

根据图表,应该避免给定盒子上低于64 ms的吞吐量,因为超过60%CPU资源将耗散到热量中!线程数越少,该数字就会越低。避免高并发始终是一个很好的检查点,无论我们是否在顺序问题上抛出过多的并行性。但是,随着CPU内核的增加,浪费60%或更多CPU资源的吞吐量水平会更高,这就是当前的未来发展方向。

图表 2:严重争用,监视器(C#锁)

基准测试:监视器(C#锁)——严重的间接争用——坏邻居

请记住,坏邻居场景是关于多个不相关的线程对的累积效应,每个线程对的争用最小。正如上面的报告中的重争用场景监视器中提到的,C#锁,低争用,可通过监视器访问的吞吐量周期,C#锁可以显着降低,而不会造成大量CPU浪费,但即使在这里我们也可以看到,监视器,C#锁,在CPU浪费中发挥作用,吞吐量低于16毫秒时是预期的两倍,因此监视器,C#锁,确实是一个坏邻居,很少有低争用的无关服务/线程可以累积将超过50%CPU资源消耗为热量仅完成了48%的有用工作。该陈述基于图表中的理想连续趋势线。

3:坏邻居,监视器(C# Lock)

概括

本文的主要目的是介绍统一并发框架/绿色超级绿色库下的基准测试方法。我们研究了重要的理想同步原语,用作与主要同步原语、MonitorC# 锁进行比较的基础。

很少有人注意到监视器、C# 锁的问题。

接下来的文章中,我们将最后一次讨论所有基准的相互比较,并有机会相互比较以发现缺点和优点。

https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值