Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/reporting-services/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 我们什么时候应该使用预取?_Performance_X86_Arm_Prefetch - Fatal编程技术网

Performance 我们什么时候应该使用预取?

Performance 我们什么时候应该使用预取?,performance,x86,arm,prefetch,Performance,X86,Arm,Prefetch,一些CPU和编译器提供预取指令。例如:内置预取。虽然GCC的文档中有一条评论,但对我来说太短了 我想知道,在prantice中,我们什么时候应该使用预取?有一些例子吗?谢谢 看来,最好的策略是根本不使用内置预取(及其朋友,内置预取)。在某些平台上,这些可能会有所帮助(甚至帮助很大)——但是,必须始终进行一些基准测试来确认这一点。真正的问题是,从长远来看,短期业绩增长是否值得付出代价 首先,有人可能会问以下问题:当这些语句被馈送到更高端的现代CPU时,它们实际上做了什么?答案是:没有人真正知道(除

一些CPU和编译器提供预取指令。例如:内置预取。虽然GCC的文档中有一条评论,但对我来说太短了


我想知道,在prantice中,我们什么时候应该使用预取?有一些例子吗?谢谢

看来,最好的策略是根本不使用内置预取(及其朋友,内置预取)。在某些平台上,这些可能会有所帮助(甚至帮助很大)——但是,必须始终进行一些基准测试来确认这一点。真正的问题是,从长远来看,短期业绩增长是否值得付出代价

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


这里列出了一个简洁的CPU功能示例(与预取无关,但仍与预取相关),该示例用于在较旧的Intel CPU上加速,但在较现代的CPU上却表现不佳:。在这种情况下,通过使用显式(“naive”)替换gcc提供的优化版memcmp,可以实现18%的性能提升也就是说)循环。

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

现在真正的问题似乎是“预回迁什么时候有用”,答案是——在任何情况下,如果内存延迟有限,访问模式不规则,硬件预回迁无法捕获(以流或步幅组织),或当您怀疑硬件无法同时跟踪太多不同的流时。
大多数编译器很少为您插入自己的预回迁,所以基本上由您来处理代码并对预回迁的有用性进行基准测试

@Mystical的链接展示了一个很好的例子,但这里有一个更直接的例子,我认为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); 
}
请注意,即使我使控制流更加复杂(额外循环嵌套级别),也会收到加速,分支预测器应该可以轻松捕获短块大小循环的模式,并且它可以避免执行不必要的预取

请注意,Ivybridge和更高版本将继续,因此硬件可能能够在这些CPU上缓解这种情况(如果有人有一个可用的CPU并且愿意尝试,我很乐意知道)。在这种情况下,我会修改基准测试,使其每第二行求和(并且每次预取都会向前看两行),这将混淆硬件预取程序

天湖结果

以下是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
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
编译标志:
-O2-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
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
时,没有预取的循环是矢量化的,但预取的额外复杂性variant循环阻止在我的gcc版本上进行矢量化

因此,
-O2
的结果可能是苹果对苹果的结果,其好处大约是我们在Leeor的Westmile上看到的一半(8%的加速比16%)。但值得注意的是,您必须小心不要更改代码生成,从而导致大幅减速

这个测试可能并不理想,因为使用
int
by
int
意味着大量的CPU开销,而不是强调内存子系统(这就是为什么矢量化帮助很大)



这篇文章“每个程序员都应该知道关于内存的知识 Ulrich Drepper’讨论了预取有利的情况; ,警告:这是一篇相当长的文章,讨论了内存体系结构/cpu如何工作等问题

如果数据与缓存线对齐,并且如果您正在加载算法即将访问的数据,则预取会提供一些信息


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

在最近的英特尔芯片上,您显然希望使用预取的一个原因是避免人为限制已实现内存带宽的CPU节能功能th。在这种情况下,与不进行预取的相同代码相比,简单预取可以将性能提高一倍,但这完全取决于所选的电源管理计划

我在中运行了一个简化版(代码)的测试,它对内存子系统的压力更大(因为预取在这方面会有所帮助、有所伤害或什么都不做)。最初的测试强调CPU与内存子系统并行,因为它将每个缓存线上的每个
int
相加。由于典型的内存读取带宽在15 GB/s的范围内,即每秒37.5亿个整数,因此最大速度的上限相当高(未矢量化的代码通常每个周期处理1
int
或更少,因此3.75 GHz CPU的CPU和内存容量大致相同)

首先,我得到的结果似乎令人满意
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
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
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
   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