2012-10-15 71 views
37

我正在尝试创建自旋锁的哑版。浏览网页时,我在x86中遇到了一个名为“PAUSE”的汇编指令,它用于向处理器提示此CPU上当前正在运行旋转锁定。英特尔手册和其它信息可用状态x86中“PAUSE”指令的用途是什么?

处理器使用此提示,以避免在 大多数情况下,大大提高处理器的性能的存储器顺序违反。对于 这个原因,建议在所有自旋等待循环中都放置一个PAUSE指令到 。该文档还提到“等待(某些 延迟)”是指令的伪执行。

上述段落的最后一行是直观的。如果我没有成功抓住锁,我必须等待一段时间,然后再次抓住锁。

然而,我们怎么在一个自旋锁的情况下是指由内存顺序违规? 是否“记忆顺序违反”指的是不正确投机负载/自旋锁后的指令店?

自旋锁问题已经被问关于堆栈溢出之前,但记忆顺序违规问题仍然没有答案(AT-至少在我的理解)。

回答

58

试想,处理器将如何执行通常的旋等待循环:

1 Spin_Lock: 
2 CMP lockvar, 0 ; Check if lock is free 
3 JE Get_Lock 
4 JMP Spin_Lock 
5 Get_Lock: 

经过几次反复分支预测器将预测条件分支(3)将永远不会被取出并在管道将填写CMP说明(2)。这一直持续到最后另一个处理器写入一个零来锁定变量。此时,我们有充满推测(即未提交)CMP指令的流水线,其中一些已经读取lockvar并向以下条件分支(3)(也是推测性的)报告了(不正确的)非零结果。这是发生内存顺序违规的时候。只要处理器“看到”外部写入(来自另一个处理器的写入),它就会在其管道中搜索推测性地访问相同内存位置但尚未提交的指令。如果发现任何这样的指令,那么处理器的推测状态是无效的,并且在流水线刷新时被擦除。

不幸的是这种情况会(很可能)重复每一个处理器上的自旋锁的等待时间,使这些锁定比他们应该要慢得多。

输入PAUSE指令:

1 Spin_Lock: 
2 CMP lockvar, 0 ; Check if lock is free 
3 JE Get_Lock 
4 PAUSE   ; Wait for memory pipeline to become empty 
5 JMP Spin_Lock 
6 Get_Lock: 

的PAUSE指令将“去管道”存储器中读取,以使得在第一实施例管道没有充满投机CMP(2)的指令等。 (也就是说它可以阻塞流水线,直到所有旧的内存指令都被提交为止。)由于CMP指令(2)是按顺序执行的,因此在CMP指令(2)读取之后发生外部写入的可能性不大(即时间窗口要短得多) lockvar,但在CMP被提交之前。

当然,“去流水线”中的自旋锁和超线程不会浪费资源,其他线程可以使用更好的情况下,也将浪费更少的能源。另一方面,在每个循环退出之前,仍有一个分支预测错误。英特尔的文档并不建议暂停消除管道冲洗,但是谁知道......

+0

(+1)感谢您的好评!我不完全理解的是,在这种情况下,由于所有这些投机阅读和推测性条件分支都完全无用,什么使管道冲洗成为这种情况下的主要成本?另外,是否有任何方法来量化冲洗的成本? – NPE

+4

@NPE从刷新恢复的时间取决于微体系结构。具有较长管道的处理器(如Core 2)明显比管道较短的处理器(如Atom)受到的影响更大。但是,在具有超线程的处理器的情况下,所有“无用的”执行指令都会从同一个内核的其他线程中分离出资源。 PAUSE指令基本上将cpu切换到另一个线程。因此,虽然锁定线程的成本是“仅限”两次管道刷新,但其他线程的成本可能更加重要(取决于锁内部花费多少时间)。 –

+0

因为CMP指令(2)按顺序执行,所以在CMP指令(2)读取lockvar之后但在提交CMP之前发生外部写入的可能性(即时间窗口短得多)是不可能的。你能解释一下吗?你提交的意思是什么? – KodeWarrior