ARM NEON简介:让移动端代码更快的秘密武器

91 阅读4分钟

从一个问题开始

假设你要复制 1000 个数字,你会怎么做?

// 最直观的写法
for (int i = 0; i < 1000; i++) {
    目标[i] = 源数据[i];
}

这样写没问题,但是…能不能更快?
答案是:可以,快很多! 这就是 NEON 的魔力所在。


什么是 NEON?

用一个生活中的例子来理解
场景:你要搬 100 个箱子
普通方法(标量计算)

一次搬 1 个箱子
需要跑 100 趟
时间:100 分钟

NEON 方法(向量计算)

一次搬 1 个箱子
需要跑 100 趟
时间:100 分钟

NEON 就是 CPU 里的"卡车"!


正式定义

  • NEON:ARM 处理器的一种特殊指令集
  • 全称:Advanced SIMD (Single Instruction, Multiple Data)
  • 翻译:一条指令,处理多个数据
  • 出现在:所有现代 ARM 设备(手机、平板、Apple M 系列芯片等)

NEON 的基本概念

1. 向量类型(Vector Types)

就像普通变量有 intfloat,NEON 有特殊的向量类型:

NEON 类型含义能装多少数据
float32x4_t4 个 float4 个(4×32 位 = 128 位)
float16x8_t8 个半精度 float8 个(8×16 位 = 128 位)
int32x4_t4 个 int4 个
uint8x16_t16 个字节16 个(16×8 位 = 128 位)

记忆技巧:

  • 32 = 每个元素 32 位
  • x4 = 有 4 个元素
  • _t = 这是一个类型(type)

2. 加载指令(Load)

把内存中的数据"装进"NEON 寄存器:

float data[4] = {1.0, 2.0, 3.0, 4.0};

// vld1q_f32 = Vector Load 1 Quad float32
float32x4_t vec = vld1q_f32(data);

// 现在 vec 里装着 4 个数字:
// vec = [1.0, 2.0, 3.0, 4.0]

函数名解读:

  • v = vector(向量)
  • ld = load(加载)
  • 1 = 加载 1 组数据
  • q = quad(4 个元素)
  • f32 = float 32 位

3. 存储指令(Store)

把 NEON 寄存器的数据"倒回"内存:

float result[4];
float32x4_t vec = ... // 假设已经有数据

// vst1q_f32 = Vector Store 1 Quad float32
vst1q_f32(result, vec);

// 现在 result 数组里有数据了

4. 运算指令

NEON 可以对向量进行各种运算:

float a[4] = {1, 2, 3, 4};
float b[4] = {5, 6, 7, 8};
float c[4];

// 加载数据
float32x4_t vec_a = vld1q_f32(a);
float32x4_t vec_b = vld1q_f32(b);

// 向量加法(一次完成 4 个加法!)
float32x4_t vec_c = vaddq_f32(vec_a, vec_b);
//                  ^^^^^^^^
//                  vector add quad float32

// 存储结果
vst1q_f32(c, vec_c);

// c = [1+5, 2+6, 3+7, 4+8] = [6, 8, 10, 12]

其他常用运算:

vsubq_f32(a, b)  // 向量减法
vmulq_f32(a, b)  // 向量乘法
vmaxq_f32(a, b)  // 向量取最大值
vminq_f32(a, b)  // 向量取最小值

完整示例:优化内存复制

让我们写一个完整的、性能优化的复制函数:

#include <arm_neon.h>

void copy_float_optimized(float* dst, const float* src, int count) {
    int i = 0;
    
    // ========== 第一阶段:处理大块数据 ==========
    // 每次处理 16 个 float(循环展开)
    int main_loop = (count / 16) * 16;  // 向下取整到 16 的倍数
    
    for (; i < main_loop; i += 16) {
        // 一次加载 16 个 float(分 4 次,每次 4 个)
        float32x4_t v0 = vld1q_f32(src + i);
        float32x4_t v1 = vld1q_f32(src + i + 4);
        float32x4_t v2 = vld1q_f32(src + i + 8);
        float32x4_t v3 = vld1q_f32(src + i + 12);
        
        // 一次存储 16 个 float
        vst1q_f32(dst + i, v0);
        vst1q_f32(dst + i + 4, v1);
        vst1q_f32(dst + i + 8, v2);
        vst1q_f32(dst + i + 12, v3);
    }
    
    // ========== 第二阶段:处理剩余的 4 的倍数 ==========
    for (; i + 3 < count; i += 4) {
        float32x4_t v = vld1q_f32(src + i);
        vst1q_f32(dst + i, v);
    }
    
    // ========== 第三阶段:处理剩余元素(<4 个)==========
    for (; i < count; i++) {
        dst[i] = src[i];  // 普通复制
    }
}

为什么要分三个阶段?

假设要复制 37 个元素:

第一阶段:处理 0-3132 个,每次 16 个)
第二阶段:处理 32-354 个,用 NEON)
第三阶段:处理 361 个,用普通方法)

常见问题 Q&A

Q1: 我的电脑能用 NEON 吗?

A: 检查方法:

#include <stdio.h>

int main() {
    #ifdef __ARM_NEON
        printf("支持 NEON!\n");
    #else
        printf("不支持 NEON\n");
        #ifdef __x86_64__
            printf("你是 x86 处理器,应该用 SSE/AVX\n");
        #endif
    #endif
    return 0;
}
  • 支持 NEON:ARM 手机、平板、Apple M1/M2/M3 Mac
  • 不支持 NEON:Intel/AMD 电脑(应该用 SSE/AVX)

Q2: 什么时候应该用 NEON?

  • 需要处理大量数据(至少几千个元素)
  • 操作比较简单(复制、加法、乘法等)
  • 操作可以并行(每个元素独立处理)

学习资源