指令级并行探索

我只是想知道是否有任何有用的工具允许我在一些算法中利用指令级并行。 更具体地说,我有一个来自多媒体领域的算法子集,我想知道在这个算法中利用ILP的最佳方法是什么。 所有这些算法都是用C实现的,所以理想情况下我将这些算法作为输入提供给某个工具,它告诉我哪些指令可以并行执行。

非常感谢任何要点!

罗伯特

问题是考虑到有多少不同的处理器类型,决定是否并行执行指令是非常困难的。 很好地理解您所针对的CPU架构将为您提供一个良好的起点来完成这类工作。 没有软件会用正确的知识击败人类的思想。

一般来说,尽管编译器和乱序执行引擎之类的工作做了很多工作,但这些工具试图尽可能多地抽象出来。 你会发现,即使完全理解这一点,你也不可能获得超过百分之几的速度提升。

如果您希望看到严重的速度提升,那么重写算法以利用多个处理器和可用的SIMD操作要好得多。 您可以单独使用SIMD看到严重的速度提升,对于许多可以同时处理多个数据元素的“多媒体算法”尤其如此。

首先,编译器和CPU本身都已经积极地重新排序指令以尽可能地利用ILP。 最有可能的是,他们比你能做得更好。

但是,人类有一些可以帮助这一过程的领域。

编译器通常对重新排序浮点计算非常保守,因为它可能会稍微改变结果。 所以例如假设这个代码:

float f, g, h, i; float j = f + g + h + i; 

您可能会得到零ILP,因为您编写的代码被评估为((f + g) + h) + i :第一次加法的结果用作下一个的操作数,其结果用于作为最后添加的操作数。 没有两个添加可以并行执行。

如果您将其写为float j = (f + g) + (h + i) ,则CPU可以并行执行f+gh+i 。 他们不依赖于彼此。

通常,阻止ILP的是依赖性。 有时它们是如上所述的算术指令之间的直接依赖关系,有时它们是存储/加载依赖关系。

与寄存器操作相比,加载和存储需要很长时间才能执行,依赖于这些操作的操作必须等到加载/存储操作完成。

因此,有时可以使用编译器可以在寄存器中缓存的临时存储数据来避免存储器访问。 同样,尽快启动负载也有助于避免其延迟阻止以下操作。

最好的技术是密切关注您的代码,并找出依赖链。 每个操作序列(其中每个操作依赖于前一个的结果)是一系列依赖项,它们永远不能并行执行。 这条链可以以某种方式分解吗? 也许通过将值存储在临时值中,或者可能通过重新计算值而不是等待从内存加载缓存版本。 也许只是通过在原始浮点示例中放置几个​​括号。

当没有依赖关系时,CPU将调度操作以并行执行。 因此,利用ILP所需要做的就是打破长依赖链。

当然,这说起来容易做起来难…… 🙂

但是,如果您花了一些时间使用分析器,并研究编译器的汇编输出,您有时可以通过手动优化代码以更好地利用ILP来获得令人印象深刻的加速。

如果我正确地读了你,你对SIMD或线程不感兴趣,只是获得正常CPU指令的最佳排序。

首先要检查的是你的编译器是否针对正确的CPU子类型。 编译器通常会重新排序指令以减少从一条指令到另一条指令的依赖关系,但编译器必须具体了解您所针对的CPU版本。 (特别是较旧的GCC有时无法检测到最近的CPU,然后针对i386进行优化)。

您可以做的第二件事是检查编译器内联决策(通过查看汇编程序)。 在算法中内联小函数可以增加代码大小,但会增加编译器优化的机会,因为可以在并行中进行多次计算。 我经常诉诸强迫内联。

最后,对于intel cpu,英特尔自己的C ++编译器声称是最好的。 他们还拥有vTune分析器,专门报告在程序的热点中有效使用ALU。

您是否有理由相信编译器在发现ILP方面做得很差? 如果您通常在算法级别上工作,那么重点应放在数据并行和高阶优化上。 优化ILP将是绝对的最后一步,并且与编译器的工作方式完全相关。 一般来说,如果你可以消除错误的数据依赖,一个体面的编译器应该为你做其余的事情。

像Acumems SlowSpotter这样的东西可能是一个帮助(除非你真的需要手动优化ILP,在这种情况下我不知道一个好工具,除非编译器可以为你吐出一个好的优化报告,IIRC Cray和SGI MIPS编译器可以生成类似的报告。

以前的答案很好。 此外,英特尔的网站还有很多值得学习的地方,如果您有预算,那么英特尔的工具值得关注。
英特尔关于优化的文章