CAS碰撞的CPU内部特征是什么?

时间:2011-04-19 17:05:32

标签: multithreading assembly concurrency x86-64 lock-free

3 个答案:

答案 0 :(得分:3)

您在这里看到的是在两个物理内核的L1缓存之间移动数据的成本。当仅使用一个核时,数据位于该L1高速缓存中,并且每个CAS以高速运行并且高速缓存中的数据。另一方面,当两个核心处于活动状态时,每次核心成功写入数据时,它将使另一个缓存无效,这将导致需要在缓存之间复制数据,然后另一个核心可以执行任何操作(通常,它会在CAS完成之前阻止等待负载)。这比实际的CAS要昂贵得多(它需要至少将数据移动到L3 cahce然后再返回到另一个L1缓存),并导致你看到的速度减慢,因为数据最终会打乒乓在两个L1缓存之间来回传递

答案 1 :(得分:0)

通过CAS,我假设你在谈论LOCK CMPXCHG

  

第二个线程开始CAS。首先   core将其缓存行发送到   第二个核心和两个核心都有   缓存行标记为共享。

您似乎认为操作开始,中断,继续。 CAS对于内存子系统是原子的。所以它一次读取值,比较和写入。没有时间段,一旦获得它,它将丢失到另一个核心的高速缓存行。这是如何运作的 ?它在指令执行期间引发处理器锁定信号,以便其他指令在存储器子系统上停止,直到高速缓存行再次可用。这就是CMPXCHG指令上有LOCK前缀的原因。您可以阅读LOCK说明以获取更多详细信息。

因此,大多数争用发生在L1上,试图获得高速缓存行的独占所有权,而该信号大多数时间都在提升。如果L1已经具有高速缓存行(例如在同一核心上有2个线程的情况下),则唯一的争用是CAS本身的持续时间,不包括跨核心的高速缓存行内存传输(因为它已经存在)。而且速度要快得多。

答案 2 :(得分:0)

所以,我一直在考虑这一切。

目前,我有两个单独的提案,用于处理CAS - “缓存锁定”和MESI。

这篇文章完全是关于缓存锁定的。

缓存锁定假定核心锁定了给定的缓存行,并且在该缓存行上尝试CAS的其他核心停止缓存仍然被释放。

此外,我还相信CAS总是在完成之前将其结果写回内存。

采用这一理论,让我们看看基准并试着解释结果。

Release 7 Lock-Free Freelist Benchmark #1

   M
   N
   S
  L3U
L2U L2U
L1D L1D
L1I L1I
 P   P
L L L L total ops,mean ops/sec per thread,standard deviation,scalability
0 0 0 1 310134488,31013449,0,1.00
0 1 0 1 136313300,6815665,38365,0.22

0 1 0 1 136401284,6820064,50706,0.22
1 1 1 1 111134328,2778358,23851,0.09

0 0 1 1 334747444,16737372,2421,0.54
1 1 1 1 111105898,2777647,40399,0.09

所以,首先是单线程案例;

L L L L total ops,mean ops/sec per thread,standard deviation,scalability
0 0 0 1 310134488,31013449,0,1.00

这里我们有最大的表现。单个线程使用每个“槽”。

现在我们来到同一个核心的两个线程;

L L L L total ops,mean ops/sec per thread,standard deviation,scalability
0 0 1 1 334747444,16737372,2421,0.54

在这里,我们当然仍然拥有相同数量的“插槽” - CAS需要的时间与它一样长 - 但我们看到它们在逻辑处理器之间均匀分布。这是有道理的;一个核心锁定高速缓存行,其他档位,第一个完成,第二个获取锁定......它们交替。目标保留在L1缓存中,缓存行处于修改状态;我们永远不需要从内存中重新读取目标,所以在这个意义上我们就像一个线程案例。

现在我们来到不同内核的两个线程;

L L L L total ops,mean ops/sec per thread,standard deviation,scalability
0 1 0 1 136401284,6820064,50706,0.22

在这里,我们看到我们的第一次大减速。我们的最大理论比例是0.5,但我们是0.22。怎么会?好吧,每个线程都试图锁定相同的缓存行(当然是在它自己的缓存中),这很好 - 但问题是当核心获得锁定时,它需要从内存中重新读取目标,因为它的缓存如果另一个核心修改了其数据副本,则该行将被标记为无效。因此,我们将速度放慢到我们必须做的内存读取。

现在我们来了四个线程,每个核心两个。

L L L L total ops,mean ops/sec per thread,standard deviation,scalability
1 1 1 1 111105898,2777647,40399,0.09

这里我们看到ops的总数实际上只比每个核心的一个线程略少,虽然当然缩放比较差,因为我们现在有四个线程,而不是两个。

在每个核心场景的一个线程中,每个CAS都以读取内存开始,因为另一个核心使CASing核心缓存线无效。

在这种情况下,当核心完成CAS并释放缓存锁定时,三个线程正在争夺锁定,两个核心在另一个核心上竞争,一个在同一核心上。所以三分之二的时间我们需要在CAS开始时重新读取内存;三分之一的时间我们没有。

所以我们应该更快。但我们实际上是SLOWER。

0% memory re-reading gives 33,474,744.4 total ops per second (two threads, same core)
66% memory re-reading, gives 11,110,589.8 total ops per second (four threads, two per core)
100% memory re-reading, gives 13,640,128.4 total ops per second (two threads, one per core)

这让我很困惑。观察到的事实不符合理论。