指令级并行探索

Posted

技术标签:

【中文标题】指令级并行探索【英文标题】:Instruction-Level-Parallelism Exploration 【发布时间】:2010-02-22 14:09:58 【问题描述】:

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

非常感谢您的任何积分!

罗伯特

【问题讨论】:

移除了“paralell-extensions”标签,这是一个 C# 语言特性。 【参考方案1】:

问题在于,考虑到有多少不同的处理器类型,决定一条指令是否将并行执行是相当困难的。对您所针对的 CPU 体系结构的深入了解将为您提供进行此类工作的良好起点。任何软件都无法在具备正确知识的情况下击败人类思维。

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

如果您想看到显着的速度提升,最好重写算法以利用多处理器和可用的 SIMD 操作。您可以看到单独使用 SIMD 显着提高速度,对于许多可以同时处理数据的多个元素的“多媒体算法”来说尤其如此。

【讨论】:

投了你一票,因为你关于软件无法击败人类思维的评论是完全错误的:你将问题与优化代码的能力混淆了。在这种情况下,硬件是最终判断可以并行执行的内容,并且此信息被精确编码到决定 ILP 的算法中。 @Amoss:即使那样我也不得不不同意你的看法。硬件可以重新调度指令,但不能执行宏优化。人脑可以在其中获胜。微观优化很有帮助,但没有宏观优化那么大。 ILP 在这一点上无关紧要。那么硬件可以重组几条指令呢?大不了。知道如何重组这些指令的人可以编写更快的代码。因此,我认为你的断言是错误的。 在这一点上,ILP 到底怎么可能无关紧要?问题是如何确定可用的 ILP。 @Amoss:好吧,换个说法……ILP背后的硬件算法与软件或人脑是否是更好的优化器无关。人脑绝对是更好的优化器。硬件很垃圾,只能在非常特定的情况下运行良好。你因为我这么说而对我投了反对票,我会继续坚持你说的完全是废话。我要重申一遍:“没有任何软件能在具备正确知识的情况下击败人类思维。” 我不反对你声称人类可以比编译器更好地优化代码;虽然这个问题仍然悬而未决,但我会接受你的主张。我的观点很简单:指令是否并行执行的问题完全可以由机器决定,并且必须由执行执行的处理器决定。这显然不同于指令是否有可能被并行执行的问题。你知道编辑你的措辞以澄清这一点很容易,然后我会投票支持你。比写侮辱要容易得多。【参考方案2】:

首先,编译器和 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 获得令人印象深刻的加速。

【讨论】:

【参考方案3】:

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

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

您可以做的第二件事是检查您的编译器内联决策(通过查看汇编器)。在算法中内联小函数可以增加代码大小,但会增加编译器优化的机会,因为可以并行完成多个计算。我经常求助于强制内联。

最后,对于英特尔 CPU,英特尔自己的 C++ 编译器声称在这方面做得最好。他们还具有 vTune 分析器,可以专门报告 ALU 在程序热点中的有效使用情况。

【讨论】:

【参考方案4】:

你有理由相信编译器在 发现 ILP?如果您通常在算法级别上工作,那么重点 应该是关于数据并行性和高阶优化。 优化 ILP 绝对是最后一步,完全是 与编译器的工作方式有关。一般来说,如果你能消除 错误的数据依赖,一个像样的编译器应该为你做剩下的事情。

像Acumems SlowSpotter 这样的东西可能会有所帮助(除非你真的 需要针对 ILP 进行手动优化,在这种情况下我不知道有什么好的 工具,除非编译器可以为 您,IIRC,Cray 和 SGI MIPS 编译器可以生成类似的报告 那个。)。

【讨论】:

【参考方案5】:

前面的答案很好。此外,在英特尔的网站上可以学到很多东西,如果您有预算,那么英特尔的工具值得一看。Intel's articles on Optimization

【讨论】:

以上是关于指令级并行探索的主要内容,如果未能解决你的问题,请参考以下文章

指令级并行

指令级并行与 SIMD

x86 - 指令级并行性 - 指令的最佳顺序

NVIDIA GPU 上的指令级并行 (ILP) 和乱序执行

体系结构复习2——指令级并行(分支预測和VLIW)

计算机系统结构的基础知识