【问题标题】:FPU,SSE single floating point. Which is faster? sub or mulFPU,SSE 单浮点。哪个更快? sub 或 mul
【发布时间】:2013-09-07 18:36:47
【问题描述】:

告诉我哪个更快:submul

我的目标平台是 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;
}

谢谢大家 ;)

【问题讨论】:

  • “更快”是什么意思?
  • 那么你有没有尝试过在线搜索?它的记录很好,速度更快。我将把答案留给读者作为练习。 :)
  • 可能有用的:instlatx64.atw.hu
  • “更快”在现代 CPU 的指令级别上不是一个有意义的术语;你需要更具体。您对吞吐量或延迟感兴趣吗?将在围绕操作的上下文中执行的指令组合是什么?
  • 现在你有了实际的代码,这变得容易多了:两个都试试。

标签: c++ c assembly floating-point


【解决方案1】:

只是为了好玩:假设您(或您的编译器)对您的两种方法进行矢量化(因为如果您追求性能,您当然会这样做),并且您的目标是最近的 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%。

【讨论】:

  • 为了让编译器进行这些优化,它可能需要更多关于对齐和(缺乏)别名的知识。可能需要将函数设为 static(因此它可以在循环中内联)并正确使用 restrict 关键字才能从编译器中获得不错的结果。
  • @R.. 说的是绝对正确的;可能不需要对齐,但如果您希望编译器为您进行矢量化,您至少应该将restrict 添加到输出指针。如果您要自己矢量化,最好让函数采用__m128 参数而不是指针。
  • 如果函数是staticout要么来自调用者中的restrict源,要么是由malloc新分配的,那么进一步使用restrict关键字编译器不需要假定没有别名。但是将restrict 添加到out 最好确保编译器在尽可能多的情况下对其进行优化。
猜你喜欢
  • 2011-03-18
  • 2023-03-18
  • 2012-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
相关资源
最近更新 更多