何时应该使用prefetch?

11

一些 CPU 和编译器提供预取指令,例如:GCC 文档 中的 __builtin_prefetch。尽管 GCC 文档中有注释,但对我来说太过简短。

我想知道,在实践中,我们何时应该使用 prefetch?有哪些示例可以提供?


3
手动预取由于存在硬件预取机制而很难获得提升效果。但这里有一个手动预取有效的例子:https://dev59.com/MWw05IYBdhLWcg3wSABH - Mysticial
1
第一个答案的第一条评论真正让人放心:"没有性能差异" :) - oakad
@oakad 噢,没错,我已经在一颗24核的Xeon CPU上测试了该程序,启用前缀使用需要1.31秒,禁用前缀使用需要1.32秒,没有明显差异。 - superK
@oakad,KaiWen,链接问题中的评论是错误的,仅从一个案例推断出预取在一般情况下没有用处是错误的逻辑。我认为我下面的例子也可以为他工作。 - Leeor
1
关于预取何时有用以及相关的权衡取舍,详见我的处理器缓存预取技术调查论文 - user984260
6个回答

14

这个问题实际上与编译器无关,它们只是提供了一些挂钩来将预取指令插入您的汇编代码/二进制文件中。不同的编译器可能提供不同的内部格式,但您可以忽略所有这些,(小心地)直接在汇编代码中添加。

现在真正的问题似乎是“何时使用预取”,答案是-在任何受内存延迟限制的情况下,在访问模式不规则且与HW预取捕获不可区分(以流或步幅组织)或者当您怀疑有太多不同流时,HW无法同时跟踪的情况下都可以使用。大多数编译器很少为您自己插入预取,因此基本上由您来测试代码并评估预取如何有用。

@Mysticial提供的链接展示了一个很好的例子,但这里有一个更简单的例子,我认为 HW 无法捕获:

#include "stdio.h"
#include "sys/timeb.h"
#include "emmintrin.h"

#define N 4096
#define REP 200
#define ELEM int

int main() {
    int i,j, k, b;
    const int blksize = 64 / sizeof(ELEM);
    ELEM __attribute ((aligned(4096))) a[N][N];
    for (i = 0; i < N; ++i) {
        for (j = 0; j < N; ++j) {
            a[i][j] = 1;
        }
    }
    unsigned long long int sum = 0;
    struct timeb start, end;
    unsigned long long delta;

    ftime(&start);
    for (k = 0; k < REP; ++k) {
        for (i = 0; i < N; ++i) {
            for (j = 0; j < N; j ++) {
                sum += a[i][j];
            }
        }
    }
    ftime(&end);
    delta = (end.time * 1000 + end.millitm) - (start.time * 1000 + start.millitm);
    printf ("Prefetching off: N=%d, sum=%lld, time=%lld\n", N, sum, delta); 

    ftime(&start);
    sum = 0;
    for (k = 0; k < REP; ++k) {
        for (i = 0; i < N; ++i) {
            for (j = 0; j < N; j += blksize) {
                for (b = 0; b < blksize; ++b) {
                    sum += a[i][j+b];
                }
                _mm_prefetch(&a[i+1][j], _MM_HINT_T2);
            }
        }
    }
    ftime(&end);
    delta = (end.time * 1000 + end.millitm) - (start.time * 1000 + start.millitm);
    printf ("Prefetching on:  N=%d, sum=%lld, time=%lld\n", N, sum, delta); 
}

我在这里做的是遍历每个矩阵行(利用HW预取器帮助连续行),但预取下一个不同页面中包含相同列索引的元素所在的下一行(HW预取器应该很难捕获)。我对数据进行求和只是为了防止被优化掉,重要的是我基本上只是遍历矩阵,应该很容易检测到,但仍然能获得加速。

采用gcc 4.8.1 -O3构建,在Intel Xeon X5670上几乎可以提升20%的性能:

Prefetching off: N=4096, sum=3355443200, time=1839
Prefetching on:  N=4096, sum=3355443200, time=1502
注意,即使我使控制流更加复杂(额外的循环嵌套层次),也会获得加速,分支预测器应该可以轻松捕捉到短块大小循环的模式,并且可以节省不需要的预取执行。
请注意,Ivybridge及以上版本在应该有一个“下一页预取器”,因此硬件可能能够在这些CPU上缓解这个问题(如果有人有可用的CPU并愿意尝试,我将非常高兴知道)。在这种情况下,我将修改基准测试以对每个第二行进行求和(并且预取将向前查看两行),那应该会使硬件预取者感到困惑。
Skylake结果
以下是来自Skylake i7-6700-HQ的一些结果,运行速度为2.6 GHz(无睿频),使用gcc进行编译:
编译标志:-O3 -march=native
Prefetching off: N=4096, sum=28147495993344000, time=896
Prefetching on:  N=4096, sum=28147495993344000, time=1222
Prefetching off: N=4096, sum=28147495993344000, time=886
Prefetching on:  N=4096, sum=28147495993344000, time=1291
Prefetching off: N=4096, sum=28147495993344000, time=890
Prefetching on:  N=4096, sum=28147495993344000, time=1234
Prefetching off: N=4096, sum=28147495993344000, time=848
Prefetching on:  N=4096, sum=28147495993344000, time=1220
Prefetching off: N=4096, sum=28147495993344000, time=852
Prefetching on:  N=4096, sum=28147495993344000, time=1253

编译标志:-O2 -march=native

Prefetching off: N=4096, sum=28147495993344000, time=1955
Prefetching on:  N=4096, sum=28147495993344000, time=1813
Prefetching off: N=4096, sum=28147495993344000, time=1956
Prefetching on:  N=4096, sum=28147495993344000, time=1814
Prefetching off: N=4096, sum=28147495993344000, time=1955
Prefetching on:  N=4096, sum=28147495993344000, time=1811
Prefetching off: N=4096, sum=28147495993344000, time=1961
Prefetching on:  N=4096, sum=28147495993344000, time=1811
Prefetching off: N=4096, sum=28147495993344000, time=1965
Prefetching on:  N=4096, sum=28147495993344000, time=1814

对于这个特定的示例,使用预取的速度要么比不使用慢大约40%,要么快8%,具体取决于您是否分别使用-O3-O2。对于-O3来说,速度变慢是由于代码生成过程中的一个小缺陷:在-O3下,没有使用预取的循环被向量化了,但是在我的gcc版本上,预取变体的循环的额外复杂性阻止了向量化。

因此,-O2结果可能更为公正,并且效益约为Leeor's Westmere所看到的一半(8%加速比16%要低)。但还值得注意的是,必须小心不要更改代码生成方式,以避免出现大的速度减慢。

这个测试可能不是最理想的,因为按照int循环会产生大量CPU开销,而不是强调内存子系统(这就是为什么向量化帮助很多)。



在第一个循环(非预取)中,您使用了未初始化的 b 变量,而且在这一行代码 sum += a[i][j+b]; 中使用了它。也许该行代码应该只是 sum += a[i][j];,因为您在该循环中没有阻塞任何东西。 - BeeOnRope
1
我添加了一些Skylake的结果。 - BeeOnRope
2
谢谢。你说的整数开销是正确的,如果我们只看每个缓存行中的一个元素,那么好处可以进一步扩大(这不是一个现实的场景,但是对于向量化可能带来的好处是一个很好的代理)。 - Leeor
我试过了(只是触碰每个缓存行),发现与节能模式有一个奇怪的交互作用,我在下面的答案中解释了一下。 - BeeOnRope
我不太熟悉C语言,但是你的代码在使用gcc 12.2.0编译时,在第15行会导致段错误。 - jberryman
@jberryman,我不确定这个对齐属性是否标准化,而且我也看到它并不总是有效(例如,在malloc上不能保证)。它只是作为一个例子 - 在分配数组时有其他方法来确保对齐 - 您可以使其大4096字节,并从ptr = ((ptr + 4095) & ~0xfffULL)开始。 - Leeor

10
在最近的英特尔芯片上,你可能想使用预取来避免 CPU 节能功能人为地限制了你实现的内存带宽 。在这种情况下,简单的预取可以将你的性能提高多达一倍,与不使用预取的相同代码相比,但这完全取决于所选的电源管理计划。
我运行了一个简化版本(代码here) 的测试 Leeor's answer,它更加强调了内存子系统(因为那里是预取会有帮助、伤害或无作为的地方)。原始测试同时压力测试了 CPU 和内存子系统,因为它将每个缓存行上的每个int相加。由于典型的内存读取带宽在15 GB/s左右,每秒处理37.5亿个整数,这就对最大速度设置了一个相当严格的限制(未矢量化的代码通常每个周期处理1 int或更少,因此3.75 GHz 的 CPU 将大约同样受到 CPU 和内存的影响)。

首先,我得到的结果似乎表明预取在我的i7-6700HQ(Skylake)上表现出色:

Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=221, MiB/s=11583
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=153, MiB/s=16732
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=221, MiB/s=11583
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=160, MiB/s=16000
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=204, MiB/s=12549
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=160, MiB/s=16000
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=200, MiB/s=12800
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=160, MiB/s=16000
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=201, MiB/s=12736
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=157, MiB/s=16305
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=197, MiB/s=12994
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=157, MiB/s=16305

通过观察数据,预取实现了略高于16 GiB/s的速度,而没有预取只有大约12.5 GiB/s,因此预取使速度提高了约30%。对吗?

不要这么快下结论。请记住,省电模式在现代芯片上有各种奇妙的交互作用,我将Linux CPU管理器从默认的powersave1更改为performance。现在我得到:

Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=155, MiB/s=16516
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=157, MiB/s=16305
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=153, MiB/s=16732
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=144, MiB/s=17777
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=144, MiB/s=17777
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=153, MiB/s=16732
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=152, MiB/s=16842
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=153, MiB/s=16732
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=153, MiB/s=16732
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=159, MiB/s=16100
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=163, MiB/s=15705
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=161, MiB/s=15900

这是一个完全随意的选择。有预取和无预取似乎表现相同。因此,硬件预取在高节能模式下可能不太积极,或者与节能有某种其他交互作用,会对显式软件预取行为产生不同影响。
调查
实际上,如果更改基准测试,预取和非预取之间的差异甚至更加明显。现有的基准测试在开启和关闭预取之间进行交替,并且事实证明,这有助于“关闭”变体,因为出现在“开启”测试中的速度增加部分地延续到随后的关闭测试2。如果仅运行“关闭”测试,则可以获得约9 GiB/s的结果:
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=280, MiB/s=9142
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=277, MiB/s=9241
Prefetching off: SIZE=256 MiB, sum=1407374589952000, time=285, MiB/s=8982

相较于预取版本的大约17 GiB/s,该版本只有约6.5 GiB/s的速度:

Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=149, MiB/s=17181
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=148, MiB/s=17297
Prefetching  on: SIZE=256 MiB, sum=1407374589952000, time=148, MiB/s=17297

所以预取版几乎快了一倍
让我们看看使用perf stat时发生了什么,对于**关闭**版本:
执行'./prefetch-test off'的性能计数器统计数据:
   2907.485684      task-clock (msec)         #    1.000 CPUs utilized                                          
 3,197,503,204      cycles                    #    1.100 GHz                    
 2,158,244,139      instructions              #    0.67  insns per cycle        
   429,993,704      branches                  #  147.892 M/sec                  
        10,956      branch-misses             #    0.00% of all branches     

...和开启版本:

   1502.321989      task-clock (msec)         #    1.000 CPUs utilized                          
 3,896,143,464      cycles                    #    2.593 GHz                    
 2,576,880,294      instructions              #    0.66  insns per cycle        
   429,853,720      branches                  #  286.126 M/sec                  
        11,444      branch-misses             #    0.00% of all branches

区别在于启用预取的版本始终以最大非Turbo频率运行,约为2.6 GHz(我已通过MSR禁用了Turbo)。然而,没有预取的版本决定以较低的速度1.1 GHz运行。这种大的CPU差异通常也反映在非核心频率上的巨大差异,这可以解释更差的带宽。

现在我们之前看到过这个, 这可能是近期英特尔芯片上的“节能Turbo”功能的结果,当它们确定进程主要受限于内存时,会尝试降低CPU频率,可能是因为在这些情况下增加CPU核心速度并不能提供太多好处。正如我们在这里所看到的,这种假设并不总是正确的,但我不确定这种权衡在一般情况下是否是一个坏选择,或者也许这种启发式只偶尔会出错。


1 我正在运行intel_pstate驱动程序,这是最近内核上Intel芯片的默认驱动程序,实现了“硬件p状态”,也称为“HWP”。使用的命令为:sudo cpupower -c 0,1,2,3 frequency-set -g performance

2 相反,“关闭”测试中的减速部分在“开启”测试中部分保留,尽管影响不那么极端,可能是因为节能的“上升”行为比“下降”更快。


1
此外,您可以使用 sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done' 直接调整 EPP 设置(适用于所有 CPU)。 (有关 Skylake 能源性能偏好的详细信息,请参见 https://patchwork.kernel.org/patch/9723429/)。 我认为 cpupower 可能没问题,但在尝试弄清楚为什么我的 4.0GHz SKL 只能 Turbo 到 3.9GHz 时,我首先遇到了 sysfs 的东西。 - Peter Cordes
使用HWP,我认为EPP就是全部了。或者“performance”调节器会禁用HWP吗? - Peter Cordes
你对3.9和4.0有什么发现?我也注意到我的SKL只在使用acpi_cpufreq而不是intel_pstate时才可以超频到3.4而不是3.5。就像只有pstate知道启用最高的p-state的诀窍,或者最高的p-state没有HWP就不可用一样。 - BeeOnRope
另一个有趣的事实是,当我在powersave上从空闲状态运行10次测试循环时,性能通常会按照典型模式从第一次迭代开始下降,例如(以MiB/s为单位):11689、10240、10322、10406、10199、10158、9770、9552、9481、9208。我理解这是powersave模式最初会升高到更高的CPU值,假设这种活动的爆发是“交互式”的,并且您可以从快速完成工作方面获得许多价值,以减少UI延迟等,但是一旦运行一段时间后,它会决定它是批处理并降低速度。 - BeeOnRope
这可能是intel_pstate之外的通用内核中的一个功能,但它可以与pstate驱动程序进行通信以满足期望。 - BeeOnRope
显示剩余5条评论

6
这是我所知道的软件预取在编程方面可能特别有用的案例简要概述,其中一些可能不适用于所有硬件。
应该从这样一个角度来阅读这个列表:最显而易见的软件预取使用场景是当软件可以预测访问流时,但这种情况并不一定对 SW 预取有明显的优势,因为乱序处理经常会产生类似的效果,它可以在现有的缺失后执行以获取更多的缺失。
因此,这个列表更多地是“考虑到 SW 预取并不像它一开始看起来那么有用,这里仍然可能有一些地方仍然有用”,通常与让乱序处理自行处理或仅使用“普通加载”来加载一些在需要之前的值相比较。

将更多的加载项放入乱序窗口中

尽管乱序处理可能会潜在地暴露与软件预取相同类型的MLP(内存级并行性),但是缓存未命中后总可能的前瞻距离存在固有限制,包括重排序缓冲区容量、加载缓冲区容量、调度器容量等等。请参阅这篇博客文章,其中一个例子说明了额外工作严重阻碍了MLP,因为CPU无法超前足够远地获取足够多的负载以同时执行。
在这种情况下,软件预取允许您在指令流中更早地有效地填充更多负载。例如,假设您有一个循环,该循环执行一次加载,然后对加载的数据进行20逐指令的操作,并且您的CPU具有100条指令的乱序缓冲区,并且负载彼此独立(例如,访问带有已知步幅的数组)。
第一次缺失后,您可以继续运行99个指令,其中包括95个非加载和5个加载指令(包括第一个加载)。因此,由于乱序缓冲区的大小限制,您的MLP固有地受到5的限制。如果相反,您将每个加载与两个软件预取到6个或更多迭代之后的位置进行配对,那么最终将得到90个非加载指令、5个加载指令和5个软件预取指令,因为所有这些加载指令都是成对出现的,所以您的MLP将翻倍,达到10的平方。
当然,每个加载指令都可以增加一个额外的预取指令: 您可以添加更多来获得更高的数字,但是当您达到机器的MLP限制并且预取开始占用您本来想要用于其他事情的资源时,会出现收益递减和负面影响的点。
这类似于软件流水线,其中您加载未来迭代的数据,然后在大量其他工作之后不再触及该寄存器。这主要用于有序机器上隐藏计算和内存延迟。即使在具有32个体系结构寄存器的RISC上,软件流水线通常也无法将负载放置得比现代机器上的最佳预取距离更远;自有序RISC早期以来,CPU在一次内存延迟期间可以执行的工作量已经增长了很多。

有序机器

并非所有机器都是乱序核心:有序CPU在某些地方仍然很常见(尤其是在x86之外),而且您还会发现“弱”乱序核心,它们没有能力运行得很远,因此在某种程度上像有序机器。
在这些机器上,软件预取可能有助于获得您否则无法访问的MLP(当然,有序机器可能不支持很多固有的MLP)。

绕过硬件预取限制

硬件预取可能有限制,您可以使用软件预取来解决这些限制。
例如,Leeor's answer提供了一个硬件预取在页面边界停止的示例,而软件预取没有这样的限制。
另一个例子可能是任何时候硬件预取过于激进或过于保守(毕竟它必须猜测您的意图):您可以使用软件预取,因为您确切地知道应用程序的行为方式。
后一种情况的示例包括预取不连续区域:例如较大矩阵的子矩阵中的行:硬件预取将无法理解“矩形”区域的边界,并且将不断预取超出每行结尾,然后需要花费一些时间来获取新的行模式。软件预取可以完全正确地处理此问题:从不发出任何无用的预取请求(但通常需要丑陋的循环分割)。
如果您进行足够的软件预取,则硬件预取在理论上应该大部分关闭,因为内存子系统的活动是它们用来决定是否激活的一种启发式方法。
反驳观点
我应该在这里指出,对于硬件预取可以提高速度的情况,软件预取并不等同于硬件预取:硬件预取可能会更快。这是因为硬件预取可以从更接近内存(例如从 L2)开始工作,它具有较低的内存延迟和访问更多缓冲区(在英特尔芯片上的所谓“超级队列”中),因此具有更多并发性。因此,如果关闭硬件预取并尝试使用纯软件预取实现 memcpy 或其他流加载入操作,则可能会发现速度较慢。
特殊加载提示
预取可能使您获得无法通过常规加载实现的特殊提示。例如,x86 有 prefetchnta、prefetcht0、prefetcht1 和 prefetchw 指令,这些指令提示处理器如何在缓存子系统中处理加载的数据。您无法通过普通加载实现相同的效果(至少在 x86 上)。

2 实际上,仅仅在循环中添加一个预取并不像看起来那么简单,因为在前五次迭代之后,加载将开始命中已经预取的值,从而将 MLP 降低到 5 - 但是这个想法仍然成立。真正的实现还涉及重新组织循环,以便可以维持 MLP(例如,每隔几次迭代将加载和预取“挤压”在一起)。


2

有些情况下,软件预取可以显著提高性能。

例如,如果您正在访问相对缓慢的存储设备(如Optane DC Persistent Memory),其访问时间为几百纳秒,如果您能够足够提前地进行预取,那么预取可以将有效延迟降低50%或更多。

目前这种情况并不常见,但如果此类存储设备成为主流,则会变得更加普遍。


1

这篇文章“关于内存,每个程序员都应该知道什么——Ulrich Drepper”讨论了预取的优势;http://www.akkadia.org/drepper/cpumemory.pdf ,警告:这是一篇相当长的文章,讨论了内存架构/ CPU工作原理等内容。

如果数据对齐到缓存行,并且您正在加载算法即将访问的数据,则预取会带来一些好处;

在任何情况下,当尝试优化高使用代码时,都应该这样做;基准测试是必须的,事情通常会与人们想象的不同。


那篇论文是在 Pentium 4 当前撰写的。更近期的 CPU 中的硬件预取比较好,因此现在通常不再需要使用软件预取。那篇论文仍然非常优秀,但请记住,软件预取建议适用于 P4,现在大多数情况下已经不再适用了。 - Peter Cordes
@PeterCordes 我不太确定这一点。例如,Linux内核仍然有很多预取调用,http://elixir.free-electrons.com/linux/latest/ident/prefetch - MichaelMoser
正确,预取仍然可以帮助非顺序访问。Drepper的文章对于现代CPU来说真正过时的是预取线程建议。P4的缓存(特别是跟踪缓存)或其他功能不足以从超线程中受益,但显然预取线程对于循环数组是值得的。随着更智能的硬件预取,您通常不应为顺序访问进行SW预取(除了pfNTA可能)。我没有意识到Linux使用了这么多的预取;我在一个btree函数中看到了一些内容;树仍然是一个很好的用例。 - Peter Cordes
1
这不是我给你的负评;我给了你一个赞,以将这个答案带回到0。但是你关于对齐数据的观点并没有太多意义。预取包含您数据的缓存行无论是否对齐都是好的。如果您的意思是当结构体跨越缓存行边界时,它可能会变得不那么有用,那么您应该这样说。自然对齐(例如16B的16B结构)足以保证不会发生这种情况;您不需要使结构体对齐为64B。(即使如此,预取一行也有所帮助。相邻行的硬件预取器甚至可以为您获取另一个) - Peter Cordes

-1

看起来,遵循的最佳策略是根本不使用__builtin_prefetch(以及它的朋友__builtin_expect)。在某些平台上,这些可能会有所帮助(甚至非常有帮助)-然而,必须始终进行一些基准测试来确认这一点。真正的问题是,短期性能收益是否值得长期麻烦。

首先,人们可以问以下问题:当输入到高端现代CPU时,这些语句实际上是做什么的?答案是:没有人真正知道(除了可能是CPU核心架构团队的少数人,但他们不会告诉任何人)。现代CPU是非常复杂的机器,能够重新排序指令,在可能未被采取的分支上执行指令的推测执行等等。此外,这种复杂行为的细节可能(并且将)在CPU代和供应商之间有很大差异(例如Intel Core vs Intel I * vs AMD Opteron;对于更分散的平台,如ARM,情况甚至更糟)。

一个不错的例子(与预取无关,但仍然)CPU功能, 过去可以加速旧的英特尔CPU的运行速度, 但在现代化的处理器上表现很差,在这里概述: http://lists-archives.com/git/744742-git-gc-speed-it-up-by-18-via-faster-hash-comparisons.html。在那个特定情况下,通过用显式的(“天真”的话)循环替换gcc优化版本提供的memcmp,可以实现18%的性能提升。


7
关于builtin-expect的立场完全错误。在您的代码中标记真正预期的分支,有助于编译器进行高级别代码优化,并且与CPU无关。同意使用builtin-prefetch,但除非知道自己在做什么,否则不应使用它。 - Konstantin Vladimirov
哪个编译器?一些编译器将__builtin_expect编码为nop。 :) - oakad
@KonstantinVladimirov 是的,似乎 likely 和 unlikely 在 Linux 内核中很有用。也许我们可以在某些平台上获得一些好处。 - superK
1
@oakad,builtin_expect并不对应任何代码。它只是编译器的提示,表明在大多数情况下(比如if子句的常规和错误处理分支),某个分支预计会在此执行。当然,它绝不可能是nop。我所说的是关于GCC的。处理builtin_expect的GCC的一部分是与体系结构无关的,被称为基本块重新排序过程(以及中间阶段的其他过程)。 - Konstantin Vladimirov
1
这个答案基本上是错误的;一些细节已经公开(例如在英特尔的优化手册中)。但基本信息是正确的:软件预取必须针对uarch进行调整,同样的代码在某些未来的uarch上可能比没有预取更糟糕。其中一部分原因是不仅仅是使用还是不使用,而是最佳预取距离是一个魔数(以字节为单位,在加载和预取之间)。请参见Linus Torvald的评论 - Peter Cordes
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接