说ARM简单的人,多半没看过T head的乱序执行表。真的,我第一次瞅见Cortex-A77那个指令调度窗口,密密麻麻的队列,差点把早饭吐出来。
但外面总有人吹“RISC干净”,“x86是CISC烂摊子”。哼。

行,咱今天捅破这层纸。
流水线不是一根水管——是三峡大坝
教科书总用工厂流水线比喻。取指、译码、执行、访存、写回——五级流水,清清爽爽。可现实呢?
Cortex-X2的流水线深度,前段就超过10级。分支预测错了,全得冲掉。flush。那代价… 就像你排了半小时队买奶茶,结果被告知“水果没了”一样绝望。
而且,ARM的复杂度根本不在于流水线级数。在于乱序执行的调度策略。你看过那本橙色的《计算机体系结构》没?里面讲Tomasulo算法。现在ARM用的就是它的魔改版——更暴力,更激进。
举个栗子,一条load指令后面跟着的add,如果寄存器没依赖,add完全可以插队先算。但万一load遇到cache miss呢?那add的结果就得等着,甚至还得回滚。这就引出个大坑——内存序。等下再说。
为了管好这些乱飞的指令,ARM引入了物理寄存器堆(那玩意儿大的吓人,单看A78,整数物理寄存器有168个之多!)。还有reorder buffer(ROB),一个巨大的环形缓冲区,专门记录指令的“出生”和“退休”。A77的ROB条目可以到160,意味着同时有160条指令在乱序飞行。这是什么概念?你家旁边那个星巴克如果同时有160杯咖啡在制作中,早就炸了。

跑分屠榜?扒一扒Spec和实际功耗的猫腻
别只看Geekbench。那个测试跑的时间太短,大核刚热乎就结束了。要看SPEC,看长时间负载。
我之前测过一台基于Cortex-A76的手机,跑SPECint2017单线程,死磕。峰值功耗冲到4.5W,持续不到3分钟就降频到1.8GHz,分数掉30%。而同期的Skylake笔记本(i5-6200U),虽然峰值功耗15W,但人家能稳定在2.7GHz跑完全程,分数几乎不降。你问哪个体验好?短时间爆发,ARM真不虚。但长时间编译、渲染——那时候你就知道为什么工作站还杵着个大风扇了。
不过苹果的M1是个怪物。它把那套乱序执行玩到极致:8发射!对,没看错,8-wide decode。ROB直接干到630左右。结果就是,4+4的大小核,Sustained multi-thread性能吊打同期4核心的11代i7,功耗却只有人家的零头。数据?Cinebench R23多核:M1跑7500分,整机功耗16W;i7-1185G7跑5600分,功耗44W。这差距让我这个老x86粉差点转阵营。
但!不是每家都有苹果的架构功力。高通的Kryo,三星的Mongoose(已凉),甚至华为的泰山,一碰大核自研就撞功耗墙。这就是第一个大坑。
三个让你怀疑人生的落地陷阱
理论说了一堆,不实操都是耍流氓。我曾在嵌入式项目里被ARM坑得欲哭无泪,总结三个十字路口,退不出来。
陷阱一:big.LITTLE的“甜点”幻象。 不是随便丢个任务给系统,它就知道该跑大核还是小核。默认的负载均衡器蠢得像头驴。曾经我用4*A55+4*A76的板子跑视频转码,调度器把解码线程平均分配到8个核上——结果大核闲得抠脚,小核忙到冒烟,帧率掉到15。气死。解决方案?强制设置CPU亲和性(affinity),用taskset或cgroup的cpuset。更优雅的是利用Energy Aware Scheduling(EAS),调优schedutil governor。关键是要压测确认你的负载模型,别信默认配置。
陷阱二:弱内存序,并发编程的幽灵。 ARM是weakly-ordered memory model。听不懂?简单说,你写了个自旋锁,在x86上跑得好好的,移到ARM上可能死锁。因为ARM允许处理器乱序执行访存指令,甚至store之间都可以重排!你不加barrier,就等着panic吧。那次我写无锁队列,在QEMU模拟x86用了两个月没事,一上真机(树莓派4)测试,三天崩一次。查了半个月,最后发现是缺少Store-Release/Load-Acquire语义。赶紧改用C11 atomics,给关键操作加上memory_order_acquire和release。ARMv8之后的指令集有专门的STLR/LDAR指令,但编译器帮你封装好了。别手动写dmb sy,又慢又难懂。
陷阱三:NEON的“免费午餐”不免费。 以为加上-mfpu=neon,编译器就自动向量化?天真。非对齐访存直接给你抛出SIGBUS。对,ARM有个奇怪的脾性:部分向量指令要求128-bit对齐。你从heap里malloc一段内存,地址可能只到8字节对齐。一跑就崩。我用过一坨画像处理代码,灰度图处理,循环里用vld1q_u8,结果图片宽不是16的倍数时,末尾像素处理就触发异常。解决方法两种:要么用posix_memalign分配内存,保证16字节对齐;要么手写边界处理,用vld1_u8非对齐版本,但开销大5%~10%。自己掂量。
还有,最近的SVE/SVE2,向量长度可变,看似灵活,实则调试疯魔。我一个学生写了段SVE的矩阵乘法,在512位宽下飞起,换成256位就默默算错,因为mask没适配好。哎。
说一千道一万,ARM的简洁是假的,复杂是真的。它就像一台精密织布机,看着花纹美,调试起来却一针都不能出差错。再回头看看那些说“ARM取代x86”的言论,我只想说——年轻人,先写个锁再说。