【问题标题】:Will fixed-point arithmetic be worth my trouble?定点算术值得我麻烦吗?
【发布时间】:2011-02-09 16:15:33
【问题描述】:

我正在研究一种应该实时运行的流体动力学 Navier-Stokes 求解器。因此,性能很重要。

现在,我正在研究许多紧密循环,每个循环都占执行时间的很大一部分:没有单一的瓶颈。这些循环中的大多数都执行一些浮点运算,但中间有很多分支。

浮点运算大多限于加法、减法、乘法、除法和比较。所有这些都是使用 32 位浮点数完成的。我的目标平台是至少带有 SSE1 指令的 x86。 (我已经在汇编器输出中验证了编译器确实会生成 SSE 指令。)

我正在使用的大多数浮点值都有一个相当小的上限,接近零值的精度并不是很重要。所以我想到了:也许切换到定点算术可以加快速度?我知道真正确定的唯一方法是衡量它,这可能需要几天时间,所以我想事先知道成功的几率。

定点在 Doom 时代风靡一时,但我不确定它在 2010 年的表现如何。考虑到现在有多少硅被注入到浮点性能中,定点是否有机会算术仍然会给我带来显着的速度提升吗?有没有人有任何可能适用于我的情况的实际经验?

【问题讨论】:

  • 分支可能是最大的性能杀手。尽量减少它。不,不要做定点。
  • 我发现相反。在某些情况下,我可以用更多的浮点运算来代替分支,但这会减慢速度。
  • 回过头来,我过去使用过专门的定点 SIMD 硬件,真是头疼!关于重新考虑定点算术,我会三思而后行。你想把大部分时间花在处理复杂的定点算术上,还是解决更大的问题上?今天的 CPU 包含更多的功能,并且包含 FPU——我会选择浮点解决方案。与其考虑定点算法,不如花时间尝试保持管道满载。
  • 另外:向 MIMD 解决方案添加几个计算节点比调整定点 SIMD 解决方案的代码更容易..
  • 另外:必须处理精确的值是一个巨大的痛苦——你不想去那里

标签: performance floating-point x86 fixed-point


【解决方案1】:

坚持使用浮点数。只有当您可以在 8 位或 16 位范围内工作并使用 SIMD 时,定点才真正有用(图像处理和音频是典型的用例)。

现代 CPU 通常有 2 个 FPU,每个时钟周期最多可以发出 2 个 FP 指令。然后,您还可以使用 4 路 FP SIMD (SSE) 进行优化。

如果您仍然难以获得良好的性能,请尝试使用更好的编译器,例如 Intel 的 ICC。此外,由于 64 位模型中寄存器数量的增加,64 位 Intel 可执行文件的速度往往比 32 位版本快一些,因此请尽可能构建 64 位。

当然,您也应该分析您的代码,以便确定热点在哪里。你没有说你正在使用什么操作系统,但VTune(Windows)、Zoom(Linux)或Shark(Mac OS X)都可以帮助你快速轻松地找到性能瓶颈。

【讨论】:

    【解决方案2】:

    您的机器已针对浮点进行了很好的优化,因此使用定点分数可能不会节省太多。

    你说没有一个瓶颈,但可能有多个,如果你设法刮掉其中任何一个,那么其他的将占用更多的剩余时间,将你的注意力吸引到它们上,这样你就可以刮掉它们也是。

    您可能已经这样做了,但我会确保不仅耗时的函数尽可能快,而且它们被调用的次数不过是必要的。

    【讨论】:

    • "... 使用定点分数可能不会节省太多。"您有任何参考资料或第一手经验来支持这一点吗?
    • @Thomas:个人经验。在 FP 处理器无处不在之前,我在图形中广泛使用了定点分数。我在阿波罗期间的仪器实验室,当时整个导航系统都是用定点分数完成的。现在有了片上 FP 硬件,我猜想,所有的归一化、加法、乘法和 NaN 检测都尽可能在组合逻辑中以全时钟速度完成,但我怀疑乘法和除法将是相同的球场,浮动与固定点 + 移位。
    【解决方案3】:

    正如其他人所说,如果您已经在使用浮点 SIMD,我怀疑您会在定点方面获得很大改进。

    您说编译器正在发出 SSE 指令,但听起来您并没有尝试编写矢量化 SSE 代码。我不知道编译器通常在这方面有多好,但有待调查。

    另外两个值得关注的领域是:

    1. 内存访问 - 如果您的所有计算都在 SSE 中完成,那么缓存未命中可能会比实际计算占用更多时间。

      1. 您可以使用例如预取数据_mm_prefetch 或 __builtin_prefetch(取决于您的编译器/平台)。
      2. 检查输入和输出之间的混叠功能;这些可能导致额外的内存读取/写入。
      3. 考虑以不同的方式存储您的数据 - 如果您的 x 坐标的流体求解器求解器独立于 y 坐标,则将它们存储在不同的数组中可能对缓存更友好。如果它们一起解决,请考虑交错(例如 x y x y...)
    2. 展开 - 您应该能够通过展开内部循环获得性能优势。目标不是(正如许多人所认为的)减少循环终止检查的数量。主要好处是允许独立指令交错,隐藏指令延迟。 here 有一个名为 VMX 优化:提升一个级别 的演讲,这可能会有所帮助;它侧重于 Xbox360 上的 Altivec 指令,但一些展开的建议也可能对 SSE 有所帮助。

    正如其他人提到的,个人资料,个人资料,个人资料。然后让我们知道什么仍然很慢:)

    PS - 在您的其他帖子之一here 中,我说服您在矩阵求解器中使用 SOR 而不是 Gauss-Seidel。现在我想起来了,你不使用三对角求解器有什么原因吗?

    【讨论】:

    • 我尝试了一点 SSE 汇编,但编译器比我更擅长。在执行 4 次同时乘法之前,我必须做太多的洗牌才能将值放在正确的位置。不过,这是我的第一个 x86 汇编代码,所以可能会挤出更多性能。
    • 我的系统足够小,可以完全放入二级缓存;总共不到 400 kB。除此之外还值得考虑 L1 缓存吗?
    • 编译器不会在-O3 展开循环吗?分支预测不会让它变得毫无意义吗?我一直认为循环展开已成为过去。顺便说一句,我确实设法通过使用红/黑方案删除了​​ SOR 求解器中的写入/读取依赖项。这产生了巨大的变化。 (当那个演讲谈到不能命名的语言特性时,我想:“goto 在这里有什么好处?我会使用模板。”下一张幻灯片:模板元编程。)无论如何,我会尝试“手动”(即模板)展开。
    • 在没有看到代码和程序集的情况下很难说循环展开。老实说,我比 SSE 更熟悉 Altivec(Xbox360 和 PS3)组件。分支预测无关紧要 - 目标是一次“运行”多个指令。如果循环中的所有指令都相互依赖,那么由于延迟,它们之间会存在间隙。展开意味着您将拥有多组独立的指令,因此可以在其他指令“之间”发出一些指令。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    相关资源
    最近更新 更多