尧图精选

谈谈64 位的 ARM 架构

🕒 发布时间:2026/9/25 5:26:44 📁 来源:尧图网络
开篇一场悄无声息的换代2013 年iPhone 5s 发布。发布会上苹果说了一句当时没什么人在意的话A7 是世界第一款 64 位手机芯片。当时的普遍反应是手机内存才 1GB要 64 位干什么 32 位能寻址 4GB还远没用满啊。十年后回头看★ 苹果全线产品从 x86 换成了自研 ARM ★ 安卓强制要求 64 位应用 ★ 亚马逊、阿里、华为都在做 ARM 服务器芯片 ★ Windows 出了 ARM 版而那个64 位从来就不只是能用更多内存。这篇文章讲清楚ARM64 到底改了什么为什么这些改动如此重要。第一部分先理清名字一、一堆容易混的术语这是入门第一个坑同一件事有好几个名字。名字指什么ARMv8-A⭐架构版本2011年发布的规范AArch64⭐64 位执行状态ARM 官方叫法AArch3232 位执行状态兼容老代码arm64⭐ 苹果/LLVM 的叫法aarch64⭐ Linux/GCC 的叫法ARM64微软的叫法arm64aarch64ARM64★ 都是同一个东西。只是不同厂商起的名字不同。二、⭐ 关键概念执行状态ARMv8 处理器可以在两种状态下跑┌────────────────────────────────────────┐ │ ARMv8 处理器 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ AArch64 │ │ AArch32 │ │ │ │ ★ 64位状态 │ │ 兼容32位 │ │ │ │ │ │ │ │ │ │ A64 指令集 │ │ A32 / T32 │ │ │ └──────────────┘ └──────────────┘ │ │ ↑ ↑ │ │ └── ★ 可以切换 ────┘ │ └────────────────────────────────────────┘⭐ 最重要的一点A64 是全新指令集❌ 常见误解 ARM64 就是 ARM32 加了个 64 位模式 ✅ 实际情况 ★ A64 是【重新设计】的指令集 ★ 和 A32 的编码格式完全不同 ★ 不是扩展是另起一套对比一下 x86 的做法x86-64AMD64★ 在 x86 上打补丁扩展 → 保留了所有历史包袱 ARM64A64 ★ 借 64 位的机会推倒重来 → 扔掉了 ARM32 的历史包袱这是 ARM64 能做得这么干净的根本原因。第二部分最直观的变化三、寄存器从 16 个到 31 个ARM32 的寄存器R0 ~ R15 ★ 只有 16 个而且 R13 SP栈指针 R14 LR返回地址 R15 PC程序计数器 ↓ ★ 真正能自由用的只有 13 个ARM64 的寄存器X0 ~ X30 ★ 31 个 64 位通用寄存器 W0 ~ W30 是它们的低 32 位 SP ★ 独立的栈指针不再占用通用寄存器 PC ★ 独立的程序计数器★ 不能直接读写了 XZR / WZR ★ 零寄存器读出来永远是 0⭐ 为什么寄存器多这么重要寄存器少 → 变量放不下 → ★ 频繁存取内存 ↓ 访问内存比访问寄存器慢几十倍 ↓ ★ 性能损失举个例子// 一个函数用了 20 个局部变量【ARM32】只有13个可用寄存器 ↓ ★ 有7个变量必须放栈上内存 ★ 每次用都要 load/store 【ARM64】有31个 ↓ ★20个变量全部放寄存器 ★ 整个函数几乎不碰内存⭐这是 ARM64 性能提升的一个主要来源。★ 经验数据仅寄存器数量翻倍这一项就能带来 5~15% 的性能提升。寄存器命名规则X0 ★ 64 位访问 W0 ★ 32 位访问就是 X0 的低 32 位 ★ 写 W0 时X0 的高 32 位会被自动清零mov x0, #0xFFFFFFFFFFFFFFFF // x0 全 1 mov w0, #1 // ★ x0 变成 0x0000000000000001 // 高 32 位被清零不是保留这个设计避免了部分寄存器写入带来的依赖问题。★ x86 上mov al, 1会保留 eax 的高位造成隐藏依赖是性能陷阱。四、⭐ 指令集的简化ARM32 的两个特色其实是包袱包袱① 条件执行ARM32 里几乎每条指令都能带条件; ARM32 CMP r0, #0 ADDEQ r1, r1, #1 ; ★ 相等才执行 SUBNE r1, r1, #1 ; ★ 不相等才执行 MOVGT r2, #5 ; ★ 大于才执行看起来很酷——不用跳转就能做分支。但代价是 每条指令都要花 4 位编码条件 → ★ 浪费了大量指令编码空间 乱序执行的处理器很难优化 → ★ 每条指令都可能不执行依赖关系复杂 现代分支预测已经很准 → ★ 条件执行的优势不再明显ARM64 的处理★ 砍掉了绝大部分条件执行 ★ 只保留少数几条CSEL、CSINC、CSET 等 csel x0, x1, x2, eq ; ★ eq 成立取 x1否则取 x2包袱② PC 可以直接操作ARM32 里 PC 就是 R15可以当普通寄存器用; ARM32 MOV pc, r0 ; ★ 直接跳转到 r0 的地址 ADD pc, pc, r1 ; ★ 甚至能算出跳转目标 LDR r0, [pc, #8] ; ★ 相对 PC 取数据问题 任何写 R15 的指令都可能是跳转 → ★ 处理器无法提前判断控制流 → ★ 流水线和分支预测极难做ARM64 的处理★ PC 不再是通用寄存器 ★ 只能通过专门的跳转指令改变 ★ 读 PC 也要用专门指令adr / adrp⭐这两项简化的共同目标★ 让处理器更容易分析指令、更容易做乱序执行和流水线优化。代价是指令数变多了一点但换来了大得多的性能空间。其他简化ARM32ARM64⭐LDM/STM一条指令存取多个寄存器⭐ 只保留LDP/STP存取一对Thumb / Thumb-2 混合16位32位指令⭐固定 32 位指令长度移位可以内嵌在很多指令里保留但简化为什么砍掉 LDM/STM一条LDM r0, {r1-r12}要访问 12 次内存。★ 这种微码级的复杂指令在乱序处理器上很难高效实现。第三部分地址空间五、64 位地址空间有多大32 位 2³² 4 GB 64 位 2⁶⁴ ★ 16 EB约 170 亿 GB但实际上没人用满 64 位★ 目前主流实现只用 48 位 → 256 TB ★ 部分支持 52 位 / 57 位为什么不用满① 用不完——256TB 对当前完全够用 ② ★ 页表层级越多地址翻译越慢 ③ ★ 高位空出来可以拿来放别的信息见下文六、⭐ 高位空着的妙用妙用① 用户/内核空间分离0x0000_0000_0000_0000 ┐ ├─ ★ 用户空间低位全 0 0x0000_FFFF_FFFF_FFFF ┘ ... 巨大的空洞 ... 0xFFFF_0000_0000_0000 ┐ ├─ ★ 内核空间高位全 1 0xFFFF_FFFF_FFFF_FFFF ┘★ 看地址最高位就知道是用户还是内核 ★ 硬件用两个独立的页表基址寄存器TTBR0/TTBR1 ★ 用户态和内核态切换不用换页表妙用② ⭐ 指针标记Top Byte IgnoreARM64 有个特性叫 TBI★ 地址的最高 8 位在访存时被硬件忽略。0x AB 00_7F_12_34_56_78 ~~ ★ 这 8 位随便填硬件访存时当它不存在用途✅ 在指针里塞额外信息类型标签、引用计数 ✅ ★ iOS 的 Objective-C tagged pointer ✅ ★ 内存调试工具HWASan用它做标记 ✅ JIT 编译器的对象头优化这是 64 位带来的一个隐藏好处★ 地址位太多了多出来的位可以当免费的元数据空间用。32 位时代 4GB 地址全都要用上根本没有余量做这种事。妙用③ ⭐ PAC指针认证ARMv8.3 引入的安全特性函数返回地址存进栈之前 ↓ ★ 用密钥算一个签名塞进地址的高位 ↓ 0x AB CD _ 00007F1234 ~~~~~ ★ 签名 ↓ 返回时校验签名 ↓ ★ 如果攻击者改了返回地址签名对不上 → 崩溃这直接打击了 ROP返回导向编程攻击。★ 苹果 A12 之后的芯片全部启用了 PAC。第四部分其他重要改进七、异常级别Exception LevelARM32 的特权模式ARM32 有 7 种处理器模式混乱且不成体系 User、FIQ、IRQ、Supervisor、Abort、Undefined、System⭐ ARM64 重新设计成四层┌──────────────────────────────────────┐ │ EL0 ★ 用户程序 │ 最低权限 ├──────────────────────────────────────┤ │ EL1 ★ 操作系统内核 │ ├──────────────────────────────────────┤ │ EL2 ★ 虚拟机监控器Hypervisor │ ├──────────────────────────────────────┤ │ EL3 ★ 安全监控器TrustZone │ 最高权限 └──────────────────────────────────────┘每一层都有独立的★ 栈指针SP_EL0 ~ SP_EL3 ★ 异常返回地址ELR_ELx ★ 系统控制寄存器SCTLR_ELx为什么这个设计重要① ★ EL2 专门给虚拟化 → 手机上跑虚拟机、云服务器都需要 → ARM32 时代虚拟化要靠软件技巧性能差 ② ★ EL3 专门给安全世界 → 指纹、支付、DRM 都跑在这里 → 即使内核被攻破也访问不到 ③ ★ 层级清晰权限边界明确这是 ARM 能进服务器市场的关键之一。★ 没有硬件虚拟化支持云计算根本没法做。八、⭐ 内存模型弱序什么是内存序多核 CPU 上一个核写的内容另一个核什么时候能看到【x86强序TSO】 ★ 基本保证先写的先被看到 ★ 硬件帮你维护顺序 ★ 代价核间通信开销大难扩展到很多核 【ARM64弱序】 ★ 不保证顺序 ★ 需要程序员显式加内存屏障 ★ 好处硬件简单、功耗低、易扩展一个会出问题的例子// 线程 A // 线程 Bdata42;if(flag1)flag1;print(data);★ 在 x86 上基本不会有问题 ★ 在 ARM64 上★ 线程 B 可能看到 flag1但 data 还是旧值原因ARM64 允许处理器/编译器重排这两条写操作 ↓ ★ flag 1 可能先写出去 ↓ 另一个核看到 flag1 时data 还没写完正确写法// ✅ 用内存屏障data42;__atomic_store_n(flag,1,__ATOMIC_RELEASE);// ★ release 语义// 线程 Bif(__atomic_load_n(flag,__ATOMIC_ACQUIRE))// ★ acquire 语义print(data);// ★ 保证能看到 data42ARM64 的屏障指令指令作用DMB数据内存屏障保证访存顺序DSB数据同步屏障更强等待完成ISB指令同步屏障刷新流水线⭐LDAR / STLR带 acquire/release 语义的访存更高效实战影响★ 从 x86 移植到 ARM64最容易出的 bug 就是内存序问题。★ 在 x86 上碰巧能跑的无锁代码到 ARM64 上会随机出错。而且这种 bug 极难复现——可能一百万次才出一次。九、SIMDNEON什么是 SIMDSIMD Single Instruction, Multiple Data ★ 一条指令处理多个数据【普通做法】 for (i 0; i 4; i) c[i] a[i] b[i]; ★ 4 次加法 【SIMD】 ★ 一条指令同时算 4 个加法ARM64 的 NEON★ 32 个 128 位向量寄存器V0 ~ V31 比 ARM32 的 NEON 多一倍 一个 128 位寄存器可以看作 16 × 8位 整数 8 × 16位 整数 4 × 32位 整数或浮点 2 × 64位 整数或浮点例子; ★ 一条指令加 4 个 32 位浮点 fadd v0.4s, v1.4s, v2.4sARM64 对浮点的重要改进【ARM32】 ★ 浮点是可选的VFP 扩展 ★ 有些芯片没有要用软件模拟 【ARM64】 ★ 浮点和 NEON 是【必选】的 ★ 所有 ARM64 处理器都有这意味着编译器可以放心生成浮点和 SIMD 代码不用考虑兼容性。更新的 SVEARMv8.2 引入 SVEScalable Vector Extension★ 向量长度可变128 ~ 2048 位 ★ 同一份代码在不同硬件上自动用不同向量宽度对比 x86 的做法 SSE(128) → AVX(256) → AVX-512(512) ★ 每次扩宽都要重写代码 SVE ★ 写一次硬件宽度变了自动适配主要用在服务器和 HPC手机上还不常见。十、原子操作ARM32/早期 ARM64 的做法★ LL/SCLoad-Linked / Store-Conditional ldxr w0, [x1] ; 独占加载 add w0, w0, #1 stxr w2, w0, [x1] ; 条件存储 cbnz w2, retry ; ★ 失败就重试问题 竞争激烈时不断重试 ★ 核数越多冲突越多性能越差⭐ ARMv8.1 的 LSE加入了真正的原子指令ldadd w0, w1, [x2] ; ★ 原子加一条指令搞定 swp w0, w1, [x2] ; ★ 原子交换 cas w0, w1, [x2] ; ★ 比较并交换这对多核服务器至关重要。★ 在 64 核、128 核的 ARM 服务器上LSE 能带来数倍的锁性能提升。第五部分兼容性十一、⭐ ARM64 怎么跑 32 位程序AArch64 状态 ←──── ★ 只能在异常返回时切换 ────→ AArch32 状态规则✅ 64 位内核可以运行 32 位应用 ❌ ★ 32 位内核不能运行 64 位应用 ❌ ★ 同一个进程内不能混用 32/64 位代码 ✅ 不同进程可以一个 32 位一个 64 位十二、⭐ 32 位兼容正在消失这是近年一个重要趋势事件时间苹果 iOS 11 彻底停止支持 32 位 App2017Google Play 要求必须提供 64 位版本2019⭐ARM Cortex-A710 之后的大核不再支持 AArch322021⭐部分新芯片完全移除 32 位支持2023为什么要移除① ★ 省晶体管、省功耗 → 一整套 32 位译码逻辑可以删掉 ② ★ 简化验证 → 少一半的状态组合要测试 ③ 生态已经完成迁移实际影响★ 如果你的应用还有 32 位的 so 库在新手机上可能根本跑不起来。必须提供 arm64-v8a 版本。第六部分为什么 ARM64 赢了十三、⭐ 三个层面的原因① 架构层干净x86-64★ 在 40 年的历史包袱上打补丁 - 变长指令1~15 字节 - 复杂的寻址模式 - 段寄存器、实模式等远古遗留 ↓ ★ 译码器极其复杂占大量晶体管和功耗 ARM64 ★ 借 64 位机会推倒重来 - 固定 32 位指令长度 - 简单的寻址模式 - 没有历史包袱 ↓ ★ 译码简单 → 可以做很宽的解码8 发射以上一个关键数据★ Apple M 系列能做到 8 宽解码x86 目前普遍在 4~6 宽。★ 原因就是变长指令让 x86 很难并行解码。② 商业层授权模式【Intel/AMD】 ★ 卖芯片 → 你只能买现成的 【ARM】 ★ 卖授权 → 苹果、高通、华为、亚马逊都能自己设计核心 → ★ 针对自己的场景深度优化结果★ 苹果为 macOS/iOS 定制 → 能效比极高 ★ 亚马逊为云服务定制 Graviton → 性价比高 ★ 高通为手机定制 → 兼顾性能和功耗③ 场景层从功耗敏感场景起家ARM 起点★ 嵌入式、手机功耗是生死线 ↓ ★ 从一开始就把每瓦性能当第一目标 ↓ 十几年打磨 ↓ ★ 等数据中心开始关心电费时ARM 已经准备好了对比x86 起点★ 桌面、服务器性能优先功耗其次 ↓ ★ 往低功耗方向走Atom一直不成功从低功耗往高性能走比从高性能往低功耗走容易。这是 ARM 能上桌面和服务器而 x86 进不了手机的根本原因。第七部分对开发者的实际影响十四、⭐ 六个要注意的地方① 内存序最重要// ❌ 在 x86 能跑在 ARM64 会随机出错volatileintflag0;intdata;// 线程 Adata42;flag1;// 可能被重排到 data 之前// ✅ 用标准原子操作std::atomicintflag{0};data42;flag.store(1,std::memory_order_release);规则★ 多线程共享数据必须用std::atomic或加锁。不要依赖volatile——它不提供内存序保证。② 数据对齐ARM64 大部分访存支持非对齐 ↓ ★ 但性能可能变差 ★ 某些指令如 LDP/STP、原子操作★ 仍然要求对齐// ✅ 显式对齐structalignas(16)Data{...};③ 指针大小// ❌ 假设指针是 4 字节inthandle(int)ptr;// 64 位下截断// ✅intptr_thandle(intptr_t)ptr;// 或用 uintptr_t / size_t④ 类型大小变化类型32位 ARM⭐ 64位 ARMLP64int44没变long4⭐8pointer4⭐8size_t4⭐8long从 4 变 8 是移植时最常见的坑。★ Windows 上long仍然是 4LLP64 模型Linux/macOS 是 8LP64。★ 想要固定宽度就用int32_t/int64_t。⑤ 内联汇编 / SIMD 代码★ x86 的 SSE/AVX 代码不能直接用 ★ 需要改写成 NEON // x86 __m128 a _mm_add_ps(b, c); // ARM NEON float32x4_t a vaddq_f32(b, c);跨平台方案✅ 用 SIMD 抽象库如 xsimd、Highway ✅ 或者写标准 C让编译器自动向量化⑥ 移动端打包必须提供 ★ arm64-v8a 主流必须 ⚠️ armeabi-v7a 老设备逐渐可省 ❌ x86 / x86_64 只有模拟器需要// Android ndk { abiFilters arm64-v8a // ★ 新项目只出 64 位 }速查表ARM64 vs ARM32 核心差异项目ARM32⭐ ARM64指令集A32 / T32⭐A64全新设计通用寄存器16 个⭐31 个指令长度32位 / 16位混合⭐固定 32 位条件执行几乎所有指令⭐基本取消PC 可直接写✅⭐❌地址空间4 GB⭐48 位起256TB特权模式7 种混乱模式⭐EL0~EL3 四层浮点/SIMD可选⭐必选向量寄存器16×128位⭐32×128位硬件虚拟化弱⭐EL2 专门支持版本特性速查版本关键特性ARMv8.0⭐ AArch64 / A64 指令集ARMv8.1⭐LSE 原子指令ARMv8.2⭐ SVE、半精度浮点ARMv8.3⭐PAC 指针认证ARMv8.4内存分区、增强虚拟化ARMv8.5⭐ MTE 内存标记抓内存 bugARMv9⭐ SVE2、CCA 机密计算移植 Checklist⭐多线程代码用std::atomic不要靠volatile⭐ 不要假设指针是 4 字节⭐ 注意long在不同平台的大小用int32_t/int64_t代替int/long检查结构体对齐和 padding⭐ SIMD 代码要改写SSE → NEON检查内联汇编⭐ 打包时必须包含 arm64-v8a⭐ 在真机上跑多线程压力测试内存序问题只在真机暴露收尾回到 2013 年那个问题“手机内存才 1GB要 64 位干什么”现在答案清楚了——64 位从来不只是为了内存★ 寄存器从 16 个变 31 个 → 性能 ★ 扔掉条件执行和可写 PC → 流水线和乱序更好做 ★ EL0~EL3 四层特权 → 虚拟化和安全 ★ 浮点/SIMD 变成必选 → 编译器敢优化 ★ 地址高位空出来 → 指针认证、内存标记 ★ 固定 32 位指令 → 可以做 8 宽解码苹果当年真正做的不是支持更多内存而是★ 借 64 位这个契机把整个指令集重新设计了一遍**。**⭐三句话总结一、ARM64 不是 ARM32 的扩展是重新设计的新指令集。★ 这是它能保持干净、进而在能效比上超越 x86 的根本原因。二、64 位最大的收益不是地址空间是寄存器数量和架构简化。★ 地址空间只是顺带的而且还没用满一半。三、对开发者而言最危险的差异是弱内存序。★ x86 上碰巧能跑的多线程代码到 ARM64 上会随机崩溃。最后一句x86 用了 40 年往上加补丁ARM 用一次机会重新出发。★ 有时候最大的优势不是你做了什么而是你没有背什么。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →