在Arm架构的可扩展向量扩展(Scalable Vector Extension, SVE)指令集中,非临时存储(Non-temporal Store)指令是一组专门为流式内存访问模式优化的存储操作。与常规存储指令不同,非临时存储会提示处理器这些数据在短期内不会被再次访问,从而允许绕过缓存层级直接写入内存。
这种技术特别适用于以下场景:
提示:缓存污染是指短期不再使用的数据占据了宝贵的缓存空间,导致真正需要缓存的数据被频繁换出,从而降低整体性能。
STNT1D指令的完整语法格式为:
code复制STNT1D { <Zt>.D }, <Pg>, [<Xn|SP>, <Xm>, LSL #3]
其二进制编码结构如下:
code复制31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 0 1 0 1 1 0 0 Rm 0 1 1 Pg Rn Zt msz<1>msz<0>
关键字段解析:
Zt:源向量寄存器,存储待写入的数据Pg:谓词寄存器,控制哪些元素需要实际写入Xn|SP:基址寄存器(通用寄存器或栈指针)Xm:索引寄存器LSL #3:索引值自动左移3位(即乘以8)STNT1D执行以下操作:
伪代码表示:
c复制CheckSVEEnabled();
elements = VL / 64;
base = (n == 31) ? SP : X[n];
offset = X[m];
src = Z[t];
for (e = 0; e < elements; e++) {
if (ActiveElement(mask, e)) {
addr = base + (UInt(offset) + e) * 8;
Mem[addr, 8, AccType_SVESTREAM] = VectorElement(src, e);
}
}
STNT1D特别适合处理双精度浮点数组或64位整数数组的流式写入。例如在矩阵乘法中,当我们需要存储计算结果且知道这些结果短期内不会被再次使用时:
assembly复制// 假设Z0存放计算结果,X0为基址,X1为索引
// P0谓词控制活跃元素
STNT1D { Z0.D }, P0, [X0, X1, LSL #3]
STNT1H指令有三种寻址模式变体:
标量+立即数偏移:
code复制STNT1H { <Zt>.H }, <Pg>, [<Xn|SP>{, #<imm>, MUL VL}]
立即数范围:-8到7
标量+标量索引:
code复制STNT1H { <Zt>.H }, <Pg>, [<Xn|SP>, <Xm>, LSL #1]
索引值自动左移1位(乘以2)
标量基址:
code复制STNT1H { <Zt>.H }, <Pg>, [<Xn|SP>]
相当于偏移为0的立即数模式
以标量+立即数模式为例,其二进制编码为:
code复制31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 0 1 0 0 1 0 0 imm4 1 1 1 Pg Rn Zt msz<1>msz<0>
关键字段:
imm4:4位有符号立即数(-8到7)与STNT1D的主要区别:
非临时存储的核心思想是绕过处理器缓存层级。现代CPU通常采用以下方式实现:
| 特性 | 常规存储 | 非临时存储 |
|---|---|---|
| 缓存分配 | 是 | 否 |
| 内存一致性 | 强一致 | 弱一致 |
| 适用场景 | 频繁访问数据 | 流式一次性数据 |
| 延迟 | 取决于缓存命中 | 稳定较高 |
| 吞吐量 | 中等 | 高 |
| 缓存污染 | 可能 | 无 |
假设我们需要处理图像行数据并直接写入输出缓冲区:
assembly复制// X0 - 输出缓冲区基址
// X1 - 当前行偏移
// Z0 - 处理后的像素数据(16位/像素)
// P0 - 活跃元素掩码
// 计算行基址
ADD X2, X0, X1, LSL #1 // 行偏移*2(每个像素2字节)
// 非临时存储整行
STNT1H { Z0.H }, P0, [X2]
在矩阵转置算法中,中间结果可以非临时存储:
assembly复制// Z0-Z3 - 转置后的4x4子矩阵(双精度)
// X0 - 临时存储区地址
// X1 - 列索引
MOV X2, #32 // 每个元素8字节,4元素行跨度
MUL X3, X1, X2 // 计算列偏移
// 存储4列数据
STNT1D { Z0.D }, P0, [X0, X3, LSL #3]
ADD X0, X0, #8 // 下一行
STNT1D { Z1.D }, P0, [X0, X3, LSL #3]
ADD X0, X0, #8
STNT1D { Z2.D }, P0, [X0, X3, LSL #3]
ADD X0, X0, #8
STNT1D { Z3.D }, P0, [X0, X3, LSL #3]
SVE支持:必须确认处理器支持SVE扩展
assembly复制MRS X0, ID_AA64PFR0_EL1
AND X0, X0, #0xF0000 // 提取SVE字段
CBNZ X0, sve_supported
索引寄存器:不能使用XZR(R31)作为索引
对齐检查:使用SP时会有额外对齐检查
元素活跃度:尽量提高谓词寄存器的活跃元素比例
内存带宽:监控内存控制器利用率
混合工作负载:避免非临时存储与常规存储的激烈竞争
问题1:存储的数据未及时写入内存
assembly复制STNT1D { Z0.D }, P0, [X0]
DMB SY // 确保存储完成
问题2:性能提升不明显
问题3:与其他核心的数据一致性问题
典型的流式处理流水线:
assembly复制// 阶段1:非临时加载输入数据
LDNT1D { Z0.D }, P0/Z, [X1] // 非临时加载
// 阶段2:数据处理
FADD Z0.D, Z0.D, Z1.D // 向量加法
// 阶段3:非临时存储结果
STNT1D { Z0.D }, P0, [X2] // 非临时存储
动态控制存储范围:
assembly复制// 生成谓词
CMPGT P0.D, X4, Z1.D // 比较生成谓词
// 条件存储
STNT1D { Z0.D }, P0, [X0] // 只存储满足条件的元素
使用MOVPRFX优化指令序列:
assembly复制MOVPRFX Z0.D, P0/Z, Z2.D // 带谓词的前缀
STNT1D { Z0.D }, P0, [X0] // 合并存储
| 架构版本 | SVE支持 | 非临时存储特性 |
|---|---|---|
| ARMv8.2 | 可选 | 基础功能 |
| ARMv8.4 | 强化 | 性能优化 |
| ARMv9.0 | 标准 | 新增相关指令 |
关键演进: