指令级并行探索

时间:2010-02-22 14:09:59

标签: c++ c parallel-processing

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

非常感谢任何要点!

罗伯特

5 个答案:

答案 0 :(得分:10)

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

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

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

答案 1 :(得分:5)

首先,编译器和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。

答案 2 :(得分:3)

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

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

之前的答案很好。此外,英特尔的网站还有很多需要学习的地方,如果您有预算,那么英特尔的工具值得一看。
Intel's articles on Optimization