C++ 忙着等待现代处理器的利弊

C++ 忙着等待现代处理器的利弊,c++,multithreading,parallel-processing,x86,computer-architecture,C++,Multithreading,Parallel Processing,X86,Computer Architecture,我正在使用busy waiting同步对关键区域的访问,如下所示: while (p1_flag != T_ID); /* begin: critical section */ for (int i=0; i<N; i++) { ... } /* end: critical section */ p1_flag++; 并行代码的执行速度比串行代码快,但是没有我预期的那么多。使用VTune放大器评测并行程序我注意到在同步指令中花费了大量时间,即,while(…)和标志更新。我不知道

我正在使用busy waiting同步对关键区域的访问,如下所示:

while (p1_flag != T_ID);

/* begin: critical section */
for (int i=0; i<N; i++) {
 ... 
}
/* end: critical section */

p1_flag++;
并行代码的执行速度比串行代码快,但是没有我预期的那么多。使用VTune放大器评测并行程序我注意到在同步指令中花费了大量时间,即,
while(…)
和标志更新。我不知道为什么我在这些“指令”上看到如此大的开销,因为区域A与区域B完全相同。我最好的猜测是,这是由于缓存一致性延迟:我使用的是Intel i7常春藤桥接机,这种微体系结构解决了L3的缓存一致性。VTune还告诉我们,
while(…)
指令正在消耗所有前端带宽,但为什么呢


澄清问题:为什么
而(…)
和更新标志指令占用了这么多执行时间?为什么
而(…)
指令会使前端带宽饱和?

在多线程应用程序中,忙等待几乎从来都不是一个好主意

当您忙着等待时,线程调度算法将无法知道您的循环正在等待另一个线程,因此它们必须分配时间,就像您的线程正在做有用的工作一样。处理器需要花费时间反复检查变量,直到它最终被另一个线程“解锁”。同时,您的另一个线程将被您繁忙的等待线程一次又一次地抢占,毫无意义

如果调度程序是基于优先级的,而繁忙等待线程的优先级更高,则这是一个更糟糕的问题。在这种情况下,低优先级线程永远不会抢占高优先级线程,因此会出现死锁情况

您应该始终使用信号量、互斥对象或消息来同步线程。我从来没有见过这样的情况,忙碌的等待是正确的解决方案


当您使用信号量或互斥量时,调度器知道在释放信号量或互斥量之前永远不会调度该线程。因此,您的线程永远不会从执行实际工作的线程上花费时间。

您所支付的开销很可能是由于在核心缓存之间来回传递sync变量造成的

缓存一致性表明,当您修改缓存线(p1_flag++)时,您需要对其拥有所有权。这意味着它将使其他内核中存在的任何副本无效,等待它将其他内核所做的任何更改写回共享缓存级别。然后,它将在
M
状态下向请求核心提供线路,并执行修改

然而,另一个核心会一直在读这一行,读到这一行会窥探第一个核心并询问它是否有这一行的副本。由于第一个核心持有该行的
M
副本,它将被写回共享缓存,核心将失去所有权

现在这取决于HW中的实际实现,但是如果在实际进行更改之前对线路进行了窥探,那么第一个核心将不得不再次尝试获得它的所有权。在某些情况下,我想这可能需要多次尝试

如果设置为使用忙等待,则至少应该在其中使用一些暂停
\u mm\u pause
intrisic,或者干脆
\u asm(“暂停”)
。这将为另一个线程提供一个获得锁的机会,并使您不再等待,同时减少繁忙等待中的CPU工作(一个无序的CPU会用这个繁忙等待的并行实例填充所有管道,消耗大量的能量-暂停会序列化它,因此在任何给定时间只能运行一个迭代-消耗更少,效果相同).

我不认为这适用于所有类型的系统。在某些情况下,您知道等待时间将非常短(几个周期),而将线程置于等待状态/等待调度程序出现的成本通常比仅仅旋转几个周期要高。Ben感谢你的回答。然而Daupic是正确的。在某些情况下,忙碌等待几个周期比为上下文切换付出代价要好。正如我在问题中所说,a和B是相同的代码,我希望它们的执行时间几乎相同,因此我不希望执行while(…)的时间太长。@johntortgo:虽然我同意dauphic是正确的,但我(根本)不同意当然,这个例子是使用旋转锁的一个很好的例子。我用过,但典型的例子是一个设备驱动程序必须等待一个或两个周期的33 MHz PCI总线。@Jerry:如果我更改这个sync.directions以获得更“高级别”的指令开销甚至更大。关键区域内的代码是循环,因此我可以展开它们以分摊同步/控制开销,但这并没有消除问题(当然它减少了开销)@约翰托尔图戈:鉴于你似乎只是在保护一个全局,然后增加它,我的想法是完全避免外部锁,只是做一个原子增量。因为你显然使用C++, STD::原子P1YAGLAG;< /COD>和刚才的代码> ++P1YFLAG;< /Cord>根本不需要任何保护。我猜是TW。o线程正在为同一缓存线的所有权而相互争斗。不过,我不是硬件级同步方面的专家。变量之间没有错误的共享。但是请注意,在两个线程都需要交换区域的时刻,同步变量是并发的。嗨,Leeor。我完全同意你的o观察!在实际更新之前,我没有考虑到缓存线可能在核心之间进行乒乓-您有多确定会发生这种情况?我考虑过使用
暂停
技巧,但是在将来,我想移植这个程序
Thread 1     Thread 2
   A        
   B            A
   A            B
   B            A
   A            B
   B            A
                B