从一个问题开始
假设你要复制 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)
就像普通变量有 int、float,NEON 有特殊的向量类型:
| NEON 类型 | 含义 | 能装多少数据 |
|---|---|---|
float32x4_t | 4 个 float | 4 个(4×32 位 = 128 位) |
float16x8_t | 8 个半精度 float | 8 个(8×16 位 = 128 位) |
int32x4_t | 4 个 int | 4 个 |
uint8x16_t | 16 个字节 | 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-31(32 个,每次 16 个)
第二阶段:处理 32-35(4 个,用 NEON)
第三阶段:处理 36(1 个,用普通方法)
常见问题 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?
- 需要处理大量数据(至少几千个元素)
- 操作比较简单(复制、加法、乘法等)
- 操作可以并行(每个元素独立处理)