哪些 CPU 架构支持比较和交换 (CAS)?

Posted

技术标签:

【中文标题】哪些 CPU 架构支持比较和交换 (CAS)?【英文标题】:Which CPU architectures support Compare And Swap (CAS)? 【发布时间】:2010-09-14 04:28:21 【问题描述】:

只是想知道哪些 CPU 架构支持比较和交换原子原语?

【问题讨论】:

【参考方案1】:

Powerpc 有更强大的可用原语:“lwarx”和“stwcx”

lwarx 从内存中加载一个值,但会记住该位置。接触该位置的任何其他线程或 CPU 都会导致“stwcx”(条件存储指令)失败。

所以 lwarx /stwcx 组合允许您实现原子递增/递减、比较和交换,以及更强大的原子操作,如“原子递增循环缓冲区索引”

【讨论】:

x86 也有原子递增/递减 (lock inc/lock dec) 和原子交换和添加 (xadd)。 lwarx 和 stwcx 的好处是 lock inc/lock dec 不是你可以用它们实现的唯一东西。它们为您提供了软件事务内存 (STM) 的构建块,具有跨多个内核的良好可扩展性。 load-store-exclusive 的另一个好处是它们不受 ABA 问题的影响,ABA 问题会使一些使用比较交换或比较和存储的算法复杂化。如果有任何东西触及加载和存储独占之间的位置,存储独占将“知道”,即使该位置被用原始值重写。 @supercat:非常好的一点,我没有想到!没有这个,ABA 问题就很难解决。 @mP.:使用数据库类比,想象一个数据库,它会在任何时间 anything 被修改时触发一个 64 位更新计数器。加载链接读取记录的状态以及更新计数器;仅当更新计数器保持特定值时,store-conditional 才会写入记录。如果每个想要更新数据库的人都通过执行加载链接并非常快速地执行存储条件来更新数据库,那么其中许多操作都会成功,但有些操作会因为不感兴趣的记录发生更新而失败。加载链接和存储条件之间的时间越长...【参考方案2】:

抱歉,有很多信。 :(

x86 ISA 中的几乎所有指令(除了所谓的字符串指令,可能还有少数其他指令),包括 CMPXCHG,在单核 CPU 的上下文中都是原子的。这是因为根据 x86 架构,CPU 在每条指令执行完成后检查到达的中断,而不是在中间。结果,可以检测到中断请求,并且仅在两个连续指令执行之间的边界处启动它的处理。因此,CPU 在执行单条指令期间采用的所有内存引用都是隔离的,并且不能被任何其他活动交错。这种行为在单核和多核 CPU 中很常见。但是,如果在单核 CPU 的上下文中只有一个系统单元执行对内存的访问,那么在多核 CPU 的上下文中,系统的多个单元同时执行对内存的访问。在这种环境下,指令隔离不足以保持一致性,因为不同 CPU 在同一时间进行的内存访问可以相互交错。由于这个额外的保护层必须应用于数据更改协议。对于 x86,这一层是锁前缀,它在系统总线上启动原子事务。

总结:如果您保证该指令访问的数据只能由一个内核访问,则使用 CMPXCHG、XADD、BTS 等不带锁定前缀的同步指令是安全且成本较低的。如果您对此不放心,请应用 lock 前缀以通过权衡性能来提供安全性。

CPU支持硬件同步主要有两种方式:

    基于原子事务。 基于缓存一致性协议。

没有人是灵丹妙药。这两种方法各有优缺点。

基于原子事务的方法依赖于对内存总线上特殊类型事务的支持。在此类事务期间,只有一个连接到总线的代理(CPU 内核)有资格访问内存。结果,一方面,总线所有者在原子事务期间所做的所有内存引用都被确保作为单个不间断事务进行。另一方面,所有其他总线代理(CPU 内核)将被强制等待原子事务完成,以恢复访问内存的能力。他们想要访问哪些内存单元并不重要,即使他们想要访问在原子事务期间总线所有者未引用的内存区域也是如此。因此,大量使用以锁定为前缀的指令会显着降低系统速度。另一方面,由于总线仲裁器根据循环调度为每个总线代理授予对总线的访问权,因此可以保证每个总线代理对内存的访问是相对公平的,并且所有代理都将被能够取得进展并以同样的速度取得进展。此外,在原子事务的情况下,ABA 问题也会发挥作用,因为原子事务本质上是非常短的(单个指令进行的内存引用很少),并且在事务期间对内存执行的所有操作仅依赖于内存区域的值,没有考虑到,内存区域是在两个事务之间被其他人访问的。 基于原子事务的同步支持的一个很好的例子是 x86 架构,其中锁定前缀指令强制 CPU 在原子事务中执行它们。

基于缓存一致性协议的方法依赖于这样一个事实,即在某一时刻内存行只能缓存在一个 L1 缓存中。缓存一致性系统中的内存访问协议类似于下一个动作序列:

    CPU A 将内存行 X 存储在 L1 高速缓存中。同时 CPU B 希望访问内存行 X。 (X --> CPU A L1) CPU B 在总线上发出内存行 X 访问事务。 (X --> CPU A L1) 所有总线代理(CPU 内核)都有一个所谓的窥探代理,它侦听总线上的所有事务并检查事务请求的内存线访问是否存储在其所有者 CPU L1 缓存中。因此,CPU A snooping agent 检测到 CPU A 拥有 CPU B 请求的内存线。 (X --> CPU A L1) CPU A 暂停由 CPU B 发出的内存访问事务。(X --> CPU A L1) CPU A 从其 L1 高速缓存中刷新 B 请求的内存行。 (X --> 记忆) CPU A 恢复先前暂停的事务。 (X --> 记忆) CPU B 从内存中获取内存行 X。 (X --> CPU B L1)

感谢该协议,CPU 内核始终访问内存中的实际数据,并且对内存的访问是按严格的顺序序列化的,一次访问是及时的。 基于缓存一致性协议的同步支持依赖于这样一个事实,即 CPU 可以轻松检测到特定内存行在两个时间点之间被访问。在对必须打开事务的行 X 的第一次内存访问期间,CPU 可以标记 L1 缓存中的该内存行必须由 snooping agent 控制。反过来,侦听代理可以在缓存行刷新期间执行检查以识别该行是否被标记为控制,如果受控行被刷新,则引发内部标志。结果,如果 CPU 在关闭事务的内存访问期间检查内部标志,它将知道受控内存线是否可以被其他人更改,并得出结论是事务必须成功完成或必须被视为失败。 这就是 LL\SC 指令类的实现方式。这种方法比原子事务更简单,并且在同步方面提供了更大的灵活性,因为与原子事务方法相比,可以在其基础上构建更多数量的不同同步原语。这种方法更具可扩展性和效率,因为它不会阻止系统所有其他部分对内存的访问。正如你所看到的,它解决了 ABA 问题,因为它基于内存区域访问检测的事实,而不是内存区域变化检测的值。对参与正在进行的事务的内存区域的任何访问都将被视为事务失败。这可能是好是坏,因为特定算法可能只对内存区域的值感兴趣,而不考虑该位置被中间人访问过,直到该访问更改内存.在这种情况下,读取中间的内存值将导致错误的否定交易失败。此外,这种方法会导致位于同一内存线上的控制流的性能大幅下降,因为它们能够不断地相互连接内存线,从而阻止彼此成功完成事务。这是一个非常重要的问题,因为在终端情况下它可以使系统处于活锁状态。 基于缓存一致性协议的同步支持通常用于 RISC CPU,因为它简单且灵活。但必须注意的是,英特尔也决定在 x86 架构中支持这种方法来支持同步。去年,英特尔宣布了 x86 架构的事务同步扩展,将在 Haswell 一代英特尔处理器中实现。因此,看起来 x86 将拥有最强大的同步支持,并允许系统开发人员利用这两种方法的优势。

【讨论】:

哇。谢谢你从微观角度解释【参考方案3】:

回答这个问题的另一种更简单的方法可能是列出不支持比较和交换(或可用于编写的加载链接/存储条件)的多处理器平台。

我知道的唯一一个是 PARISC,它只有一个原子明文指令。这可用于构造互斥体(前提是在 16 字节边界上对齐字)。此架构上没有 CAS(与 x86、ia64、ppc、sparc、mips、s390、...不同)

【讨论】:

旧版 ARM 是第二个没有完整 CAS 的平台:gcc.gnu.org/wiki/Atomic 说 arm, pa (PA-RISC), sh 他们“没有原生原子指令,但 linux 内核提供对软件原子的支持操作。”【参考方案4】:

一些人评论/询问关于 cmpxchg 在 x86/x64 上是否需要“锁定”前缀。对于多核机器,答案是肯定的。该指令对于没有锁的单核机器是完全原子的。

自从我深入研究这些东西以来已经有一段时间了,但我似乎记得该指令在技术上是可重新启动的 - 它可以在飞行中中止指令(如果它还没有任何副作用)以避免延迟中断处理时间过长。

【讨论】:

【参考方案5】:

Intel x86 具有此支持。 IBM 在它的Solaris to Linux Porting Guide 中给出了这个例子:

bool_t My_CompareAndSwap(IN int *ptr, IN int old, IN int new)

        unsigned char ret;

        /* Note that sete sets a 'byte' not the word */
        __asm__ __volatile__ (
                "  lock\n"
                "  cmpxchgl %2,%1\n"
                "  sete %0\n"
                : "=q" (ret), "=m" (*ptr)
                : "r" (new), "m" (*ptr), "a" (old)
                : "memory");

        return ret;

【讨论】:

您能进一步解释一下吗?为什么要使用lock 指令。 cmpxchg 本身是原子的吗?上面的代码是只“触及”单个内存位置,还是实现了内存栅栏并触发全局缓存一致性协议?【参考方案6】:

从 ARMv6 架构开始,ARM 具有可用于实现原子比较交换操作的 LDREX/STREX 指令。

【讨论】:

ARM的LDREX/STREX和PPC的LWARX/STWCX类似吗? 我相信是这样 - ARM 技术参考手册对 LDREX/STREX 的解释相当复杂(对于 PowerPC,我将按照 Jeff Koftinoff 的解释进行解释),因此细节上可能存在一些差异。 【参考方案7】:

为了完成这个列表,MIPS 有 Load Linked (ll) 和 Store Conditional (sc) 指令,它们从内存中加载一个值,然后在没有其他 CPU 访问该位置时有条件地存储。确实,您可以使用这些指令来执行交换、递增和其他操作。然而,缺点是大量 CPU 非常频繁地使用锁,你会陷入活锁:条件存储会经常失败,需要另一个循环再试一次,这会失败,等等。

如果这些情况被认为足够重要,需要担心的话,软件 mutex_lock 的实现可能会变得非常复杂,尝试实现指数退避。在我使用 128 个内核的一个系统中,它们是。

【讨论】:

我同意,在使用非锁定数据结构(通常使用 CAS)时,必须非常仔细地观察锁定争用。感谢您的来信。【参考方案8】:

x86 和 Itanium 有 CMPXCHG(比较和交换)

【讨论】:

请注意老硬件黑客,这个指令直到 i486 才添加。 这是给年轻黑客的注意事项,不是吗? CMPXCHG 是原子操作,还是必须使用 LOCK? CMPXCHG 对单 CPU 内的抢占是原子的,但添加 LOCK 将使其对跨多 CPU 的原子性。这是因为“锁定”实际上锁定了所有 CPU 对内存总线的访问。 heather.cs.ucdavis.edu/~matloff/50/PLN/lock.pdf【参考方案9】:

Compare and swap 于 1973 年被添加到 IBM 大型机中。它(以及比较 double 和 swap)仍然在 IBM 大型机上(以及最近的多处理器功能,如 PLO - 执行锁定操作)。

【讨论】:

据说 CAS(比较和交换)是发明指令的人的首字母。 “查理”。【参考方案10】:

Sparc v9 有一个 cas 指令。 SPARC v9 architecture manual 讨论了附件 J 中 CAS 指令的使用,具体看示例 J.11 和 J.12。

我相信指令的名称实际上是“casa”,因为它可以访问当前地址空间或备用地址空间。 "cas" 是一个访问当前 ASI 的汇编宏。

developers.sun.com 上还有一篇文章讨论 Sparc 处理器多年来实现的各种原子指令,包括 cas。

【讨论】:

这是什么?能给个链接吗? 请注意,虽然 x86 具有双字 CAS,而其他非 SPARC CPU 具有 ll/cs - 两者都使用计数器解决 ABA。单字 CAS 不允许使用计数器解决 ABA,因此与其他架构相比,SPARC 处于不利地位。 Sparc v8 或 Sparc v7 怎么样?

以上是关于哪些 CPU 架构支持比较和交换 (CAS)?的主要内容,如果未能解决你的问题,请参考以下文章

与 CAS(比较和交换)相比,LL/SC 的优势是啥?

CAS到底是怎么回事

CAS Compare and Swap 比较后交换

了解CAS

CAS

比较并交换不适用于许多核心