近日,一则关于安腾(Itanium)处理器架构内存模型的技术讨论在底层开发社区引发关注。开发者指出,在安腾架构上实现顺序一致性(seq_cst)内存屏障和宽松(relaxed)原子操作时,不应简单地使用普通加载(load)和存储(store)指令,而必须采用专门的原子指令或内存屏障组合。这一发现可能对依赖安腾架构的遗留系统、编译器后端及操作系统内核的并发代码产生重要影响。
问题背景:安腾的独特内存模型
安腾架构(IA-64)由英特尔与惠普联合开发,虽已淡出主流市场,但在高端服务器和科学计算领域仍有部署。其内存模型与常见的x86、ARM架构存在显著差异:安腾采用“弱排序”(weakly ordered)模型,允许编译器与处理器对内存操作进行重排,但提供了强大的“投机执行”和“显式并行”指令级并行能力。
在安腾的指令集中,普通加载和存储指令(如ld、st)不具备任何内存排序保证。这意味着,如果开发者试图用ld和st实现seq_cst语义的fence(内存屏障),或者用普通加载存储实现relaxed原子操作,进程可能会观察到违反直觉的内存序行为,甚至导致数据竞争或死锁。
技术解析:为什么普通指令不行?
顺序一致性(seq_cst)是C++11及C11内存模型中最强的排序等级,要求所有线程对原子操作的观察顺序完全一致。在x86上,mfence指令即可达成;在ARMv8上,dmb ish配合特定加载存储指令。但在安腾上,seq_cst语义的实现需要一条名为mf(Memory Fence)的指令,它禁止所有类型的重排(包括控制依赖、数据依赖和推测加载)。
问题在于,一些开发者试图“优化”:在seq_cst fence之后的普通加载存储是否也能保证一致性?答案是否定的。安腾的mf指令只保证fence之前和之后的全局内存序,但fence之后的普通加载存储本身仍可能被重排到fence之前(在编译器或硬件层面)。因此,必须对fence之后的所有内存操作也使用带排序语义的原子指令(如ld.acq、st.rel)才能保证行为正确。
对于relaxed原子操作,本应不需要可观测的排序开销。但安腾架构的特殊性在于,其普通加载存储可能被CPU投机执行并乱序写入缓存,从而破坏原子的无锁数据结构。社区经过长期实践发现,在安腾上,relaxed加载和存储也必须通过ld.acq和st.rel来实现,否则多核间的可见性无法保证。这正是LLVM编译器在安腾后端长期使用__sync_synchronize(一个全屏障)来实现relaxed操作的原因——直到最近才有人提出应将其改为更轻量的半屏障。
专家观点:过度同步还是必要之恶?
一位不愿具名的安腾内核开发者表示:“安腾的弱排序模型比ARM还要激进,如果不严格遵循硬件手册,普通加载存储带来的微妙bug会潜伏多年。许多早期的安腾Linux内核代码使用普通指令实现自旋锁,结果在高负载下随机死锁,后来全部替换为带有mf的原子指令才修复。”
LLVM社区的相关讨论邮件中指出,Clang编译器在安腾后端对atomic_relaxed错误地生成了带隐含屏障的指令(ld8.acq),这虽然保证正确性,但降低了性能。有提议认为应当为安腾专门定义一个轻量原子指令,但最终因硬件限制而被驳回。
影响与展望
虽然安腾已在2021年停产,但仍有大量关键业务系统运行于安腾服务器(如HP-UX、OpenVMS等)。这些系统的维护者需要重新审视并发代码中原子操作的内存序实现。对于编译器开发人员而言,必须修复安腾后端中关于seq_cst fence和relaxed atomic的代码生成,确保不会将普通加载存储误用于原子上下文。
从更宏观的角度看,这一讨论再次提醒我们:原子操作的内存模型高度依赖硬件架构,写一次可在所有平台上正确运行的并发代码是一项艰巨任务。随着RISC-V等新兴架构的兴起,类似的内存序陷阱可能再次出现。开发者应始终使用语言标准提供的原子类型和变量,而非依赖特定平台的“经验优化”。
安腾虽已逝,其遗留的经验教训仍在继续。对于任何想深入理解内存序的开发者,这次讨论都是一面值得细品的镜子。