告诉我哪一个更快:sub
或mul
?
我的目标平台是X86; FPU和SSE。
示例:
'LerpColorSolution1'使用乘法。
'LerpColorSolution2'使用减法。
哪个更快?
void LerpColorSolution1(const float* a, const float* b, float alpha, float* out)
{
out[0] = a[0] + (b[0] - a[0]) * alpha;
out[1] = a[1] + (b[1] - a[1]) * alpha;
out[2] = a[2] + (b[2] - a[2]) * alpha;
out[3] = a[3] + (b[3] - a[3]) * alpha;
}
void LerpColorSolution2(const float* a, const float* b, float alpha, float* out)
{
float f = 1.0f - alpha;
out[0] = a[0]*f + b[0] * alpha;
out[1] = a[1]*f + b[1] * alpha;
out[2] = a[2]*f + b[2] * alpha;
out[3] = a[3]*f + b[3] * alpha;
}
感谢所有人;)
答案 0 :(得分:10)
只是为了好玩:假设你(或你的编译器)对你的两种方法进行了矢量化(因为当然你会追逐性能),并且你的目标是最近的x86处理器......
将“LerpColorSolution1”直接翻译成AVX指令如下:
VSUBPS dst, a, b // a[] - b[]
VSHUFPS alpha, alpha, alpha, 0 // splat alpha
VMULPS dst, alpha, dst // alpha*(a[] - b[])
VADDPS dst, a, dst // a[] + alpha*(a[] - b[])
此序列的长延迟链是sub-mul-add,在最新的Intel处理器上总延迟为3 + 5 + 3 = 11个周期。吞吐量(假设您只执行这些操作)受端口1利用率的限制,理论峰值为每两个周期一个LERP。 (我故意忽略加载/存储流量,并专注于此处执行的数学运算。)
如果我们看一下你的“LerpColorSolution2”:
VSHUFPS alpha, alpha, alpha, 0 // splat alpha
VSUBPS dst, one, alpha // 1.0f - alpha, assumes "1.0f" kept in reg.
VMULPS tmp, alpha, b // alpha*b[]
VMULPS dst, dst, a // (1-alpha)*a[]
VADDPS dst, dst, tmp // (1-alpha)*a[] + alpha*b[]
现在长延迟链是shuffle-sub-mul-add,其总延迟为1 + 3 + 5 + 3 = 12个周期;吞吐量现在受端口0和1的限制,但每两个周期仍然有一个LERP的峰值。您需要为每个LERP操作停用一个额外的μop,这可能会使吞吐量略微变慢,具体取决于周围环境。
所以你的第一个解决方案稍好一点; (这并不奇怪 - 即使没有这些分析细节,粗略的指导方针“更少的操作更好”是一个很好的经验法则。)
Haswell显着倾向于第一种解决方案;使用FMA,它在每个端口0,1和5上只需要一个μop,允许每个周期一个LERP的理论吞吐量;虽然FMA也改进了解决方案2,但它仍然需要4μs,包括需要在端口0或1上执行的3μs。这将解决方案2限制为每1.5个周期一个LERP的理论峰值 - 比解决方案1慢50%。