Arm v7 指令集为反倒数平方根计算提供了快速指令vrsqrte_f32 用于两个同时近似,vrsqrteq_f32 用于四个近似。 (标量变量vrsqrtes_f32 仅适用于 Arm64 v8.2)。
那么结果可以简单地用x * vrsqrte_f32(x);计算,在整个正值x范围内的相对准确度优于0.33%。见https://www.mdpi.com/2079-3197/9/2/21/pdf
ARM NEON 指令 FRSQRTE 给出 8.25 位正确的结果。
在x==0 vrsqrtes_f32(x) == Inf,所以 x*vrsqrtes_f32(x) 将是 NaN。
如果x==0的值是不可避免的,那么最优的两条指令序列需要多一点调整:
float sqrtest(float a) {
// need to "transfer" or "convert" the scalar input
// to a vector of two
// - optimally we would not need an instruction for that
// but we would just let the processor calculate the instruction
// for all the lanes in the register
float32x2_t a2 = vdup_n_f32(a);
// next we create a mask that is all ones for the legal
// domain of 1/sqrt(x)
auto is_legal = vreinterpret_f32_u32(vcgt_f32(a2, vdup_n_f32(0.0f)));
// calculate two reciprocal estimates in parallel
float32x2_t a2est = vrsqrte_f32(a2);
// we need to mask the result, so that effectively
// all non-legal values of a2est are zeroed
a2est = vand_u32(is_legal, a2est);
// x * 1/sqrt(x) == sqrt(x)
a2 = vmul_f32(a2, a2est);
// finally we get only the zero lane of the result
// discarding the other half
return vget_lane_f32(a2, 0);
}
当然,这种方法的吞吐量几乎是两倍
void sqrtest2(float &a, float &b) {
float32x2_t a2 = vset_lane_f32(b, vdup_n_f32(a), 1);
float32x2_t is_legal = vreinterpret_f32_u32(vcgt_f32(a2, vdup_n_f32(0.0f)));
float32x2_t a2est = vrsqrte_f32(a2);
a2est = vand_u32(is_legal, a2est);
a2 = vmul_f32(a2, a2est);
a = vget_lane_f32(a2,0);
b = vget_lane_f32(a2,1);
}
如果您可以直接使用 float32x2_t 或 float32x4_t 输入和输出,那就更好了。
float32x2_t sqrtest2(float32x2_t a2) {
float32x2_t is_legal = vreinterpret_f32_u32(vcgt_f32(a2, vdup_n_f32(0.0f)));
float32x2_t a2est = vrsqrte_f32(a2);
a2est = vand_u32(is_legal, a2est);
return vmul_f32(a2, a2est);
}
这个实现给出了sqrtest2(1) == 0.998 和sqrtest2(400) == 19.97(在带有arm64 的MacBook M1 上测试)。由于无分支且无 LUT,这可能具有恒定的执行时间,假设所有指令都以恒定数量的周期执行。