3

告诉我哪个更快: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;
}

谢谢大家 ;)

4

1 回答 1

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 个微操作,包括需要在端口 0 或 1 上执行的三个。这将解决方案 2 限制为每 1.5 个周期一个 LERP 的理论峰值——比解决方案 1 慢 50%。

于 2013-09-07T19:22:46.160 回答