首先,编译器和 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+g 和h+i。它们不相互依赖。
一般来说,阻止 ILP 的是依赖关系。有时它们是上述算术指令之间的直接依赖关系,有时它们是存储/加载依赖关系。
与寄存器内操作相比,加载和存储需要很长时间才能执行,并且依赖于这些操作的操作必须等到加载/存储操作完成。
因此,将数据存储在编译器可以缓存在寄存器中的临时文件中,有时可以用来避免内存访问。同样,尽快开始加载也有助于避免延迟阻塞后续操作。
最好的技术是仔细查看您的代码,并找出依赖链。每个操作序列都依赖于前一个操作的结果,这是一个依赖链,永远不能并行执行。这条链可以以某种方式打破吗?可能是通过将值存储在临时文件中,或者可能通过重新计算一个值而不是等待从内存中加载缓存的版本。也许只需像原始浮点示例中那样放置几个括号。
当没有依赖关系时,CPU 将调度操作并行执行。因此,利用 ILP 所需要做的就是打破长依赖链。
当然,说起来容易做起来难... :)
但是,如果您花一些时间使用分析器,并研究编译器的汇编输出,您有时可以通过手动优化代码以更好地利用 ILP 获得令人印象深刻的加速。