C L1内存带宽:使用相差4096+;64字节

C L1内存带宽:使用相差4096+;64字节,c,caching,memory,x86,avx,C,Caching,Memory,X86,Avx,我希望在英特尔处理器上实现以下操作的最大带宽 for(int i=0; i<n; i++) z[i] = x[i] + y[i]; //n=2048 当我这样做时,我得到了每个系统预期峰值带宽的50% 峰值计算为频率*平均字节/时钟周期。每个系统的平均字节/时钟周期为: Core2: two 16 byte reads one 16 byte write per 2 clock cycles -> 24 bytes/clock cycle SB/IB: two 32 byt

我希望在英特尔处理器上实现以下操作的最大带宽

for(int i=0; i<n; i++) z[i] = x[i] + y[i]; //n=2048
当我这样做时,我得到了每个系统预期峰值带宽的50%

峰值计算为
频率*平均字节/时钟周期
。每个系统的平均字节/时钟周期为:

Core2: two 16 byte reads one 16 byte write per 2 clock cycles     -> 24 bytes/clock cycle
SB/IB: two 32 byte reads and one 32 byte write per 2 clock cycles -> 48 bytes/clock cycle
Haswell: two 32 byte reads and one 32 byte write per clock cycle  -> 96 bytes/clock cycle
这意味着,例如,在Haswell I上,我只观察到48字节/时钟周期(可能在一个时钟周期中有两次读取,在下一个时钟周期中有一次写入)

我打印出了
b-a
c-b
地址的差异,每个都是8256字节。值8256是8192+64。因此,它们都比数组大小(8192字节)大一个缓存线

一时兴起,我试着这样分配内存

const int k = 0;
char *mem = (char*)_mm_malloc(1<<18,4096);
char *a = mem;
char *b = a+n*sizeof(float)+k*64;
char *c = b+n*sizeof(float)+k*64;
float *x = (float*)a; float *y = (float*)b; float *z = (float*)c;
编辑:这里是关于
k=0
k=1
的每个系统的效率表

               k=0      k=1        
Westmere:      99%      66%
Ivy Bridge:    98%      44%
Haswell:       90%      49%
如果我假设对于
k=1
,写入和读取不能在同一时钟周期内发生,我想我可以解释这些数字

       cycle     Westmere          Ivy Bridge           Haswell
           1     read  16          read  16 read  16    read  32 read 32
           2     write 16          read  16 read  16    write 32
           3                       write 16
           4                       write 16  

k=1/k=0 peak    16/24=66%          24/48=50%            48/96=50%
这个理论很有效。常春藤桥比我想象的要低一点,但是常春藤桥 遭受银行缓存冲突,其他人不这样做,这可能是另一个需要考虑的因素。 下面是自己测试的工作代码。在没有AVX的系统上,使用
g++-O3 sum.cpp
编译,否则使用
g++-O3-mavx sum.cpp
编译。尝试改变值
k

//sum.cpp
#include <x86intrin.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#define TIMER_TYPE CLOCK_REALTIME

double time_diff(timespec start, timespec end)
{
    timespec temp;
    if ((end.tv_nsec-start.tv_nsec)<0) {
        temp.tv_sec = end.tv_sec-start.tv_sec-1;
        temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
    } else {
        temp.tv_sec = end.tv_sec-start.tv_sec;
        temp.tv_nsec = end.tv_nsec-start.tv_nsec;
    }
    return (double)temp.tv_sec +  (double)temp.tv_nsec*1E-9;
}

void sum(float * __restrict x, float * __restrict y, float * __restrict z, const int n) {
    #if defined(__GNUC__)
    x = (float*)__builtin_assume_aligned (x, 64);
    y = (float*)__builtin_assume_aligned (y, 64);
    z = (float*)__builtin_assume_aligned (z, 64);
    #endif
    for(int i=0; i<n; i++) {
        z[i] = x[i] + y[i];
    }
}

#if (defined(__AVX__))
void sum_avx(float *x, float *y, float *z, const int n) {
    float *x1 = x;
    float *y1 = y;
    float *z1 = z;
    for(int i=0; i<n/64; i++) { //unroll eight times
        _mm256_store_ps(z1+64*i+  0,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 0), _mm256_load_ps(y1+64*i+  0)));
        _mm256_store_ps(z1+64*i+  8,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 8), _mm256_load_ps(y1+64*i+  8)));
        _mm256_store_ps(z1+64*i+ 16,_mm256_add_ps(_mm256_load_ps(x1+64*i+16), _mm256_load_ps(y1+64*i+ 16)));
        _mm256_store_ps(z1+64*i+ 24,_mm256_add_ps(_mm256_load_ps(x1+64*i+24), _mm256_load_ps(y1+64*i+ 24)));
        _mm256_store_ps(z1+64*i+ 32,_mm256_add_ps(_mm256_load_ps(x1+64*i+32), _mm256_load_ps(y1+64*i+ 32)));
        _mm256_store_ps(z1+64*i+ 40,_mm256_add_ps(_mm256_load_ps(x1+64*i+40), _mm256_load_ps(y1+64*i+ 40)));
        _mm256_store_ps(z1+64*i+ 48,_mm256_add_ps(_mm256_load_ps(x1+64*i+48), _mm256_load_ps(y1+64*i+ 48)));
        _mm256_store_ps(z1+64*i+ 56,_mm256_add_ps(_mm256_load_ps(x1+64*i+56), _mm256_load_ps(y1+64*i+ 56)));
    }
}
#else
void sum_sse(float *x, float *y, float *z, const int n) {
    float *x1 = x;
    float *y1 = y;
    float *z1 = z;
    for(int i=0; i<n/32; i++) { //unroll eight times
        _mm_store_ps(z1+32*i+  0,_mm_add_ps(_mm_load_ps(x1+32*i+ 0), _mm_load_ps(y1+32*i+  0)));
        _mm_store_ps(z1+32*i+  4,_mm_add_ps(_mm_load_ps(x1+32*i+ 4), _mm_load_ps(y1+32*i+  4)));
        _mm_store_ps(z1+32*i+  8,_mm_add_ps(_mm_load_ps(x1+32*i+ 8), _mm_load_ps(y1+32*i+  8)));
        _mm_store_ps(z1+32*i+ 12,_mm_add_ps(_mm_load_ps(x1+32*i+12), _mm_load_ps(y1+32*i+ 12)));
        _mm_store_ps(z1+32*i+ 16,_mm_add_ps(_mm_load_ps(x1+32*i+16), _mm_load_ps(y1+32*i+ 16)));
        _mm_store_ps(z1+32*i+ 20,_mm_add_ps(_mm_load_ps(x1+32*i+20), _mm_load_ps(y1+32*i+ 20)));
        _mm_store_ps(z1+32*i+ 24,_mm_add_ps(_mm_load_ps(x1+32*i+24), _mm_load_ps(y1+32*i+ 24)));
        _mm_store_ps(z1+32*i+ 28,_mm_add_ps(_mm_load_ps(x1+32*i+28), _mm_load_ps(y1+32*i+ 28)));
    }
}
#endif

int main () {
    const int n = 2048;
    const int k = 0;
    float *z2 = (float*)_mm_malloc(sizeof(float)*n, 64);

    char *mem = (char*)_mm_malloc(1<<18,4096);
    char *a = mem;
    char *b = a+n*sizeof(float)+k*64;
    char *c = b+n*sizeof(float)+k*64;

    float *x = (float*)a;
    float *y = (float*)b;
    float *z = (float*)c;
    printf("x %p, y %p, z %p, y-x %d, z-y %d\n", a, b, c, b-a, c-b);

    for(int i=0; i<n; i++) {
        x[i] = (1.0f*i+1.0f);
        y[i] = (1.0f*i+1.0f);
        z[i] = 0;
    }
    int repeat = 1000000;
    timespec time1, time2;

    sum(x,y,z,n);
    #if (defined(__AVX__))
    sum_avx(x,y,z2,n);
    #else
    sum_sse(x,y,z2,n);
    #endif
    printf("error: %d\n", memcmp(z,z2,sizeof(float)*n));

    while(1) {
        clock_gettime(TIMER_TYPE, &time1);
        #if (defined(__AVX__))
        for(int r=0; r<repeat; r++) sum_avx(x,y,z,n);
        #else
        for(int r=0; r<repeat; r++) sum_sse(x,y,z,n);
        #endif
        clock_gettime(TIMER_TYPE, &time2);

        double dtime = time_diff(time1,time2);
        double peak = 1.3*96; //haswell @1.3GHz
        //double peak = 3.6*48; //Ivy Bridge @ 3.6Ghz
        //double peak = 2.4*24; // Westmere @ 2.4GHz
        double rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime;
        printf("dtime %f, %f GB/s, peak, %f, efficiency %f%%\n", dtime, rate, peak, 100*rate/peak);
    }
}
======
k=0
======
load x+(0*64+0)*4  = x+0 where x is 4k aligned    0000 000|0 0000
load y+(0*64+0)*4  = y+0 where y is 4k aligned    0000 000|0 0000
store z+(0*64+0)*4 = z+0 where z is 4k aligned    0000 000|0 0000
load x+(0*64+8)*4  = x+32 where x is 4k aligned   0000 001|0 0000
load y+(0*64+8)*4  = y+32 where y is 4k aligned   0000 001|0 0000
store z+(0*64+8)*4 = z+32 where z is 4k aligned   0000 001|0 0000
load x+(0*64+16)*4 = x+64 where x is 4k aligned   0000 010|0 0000
load y+(0*64+16)*4 = y+64 where y is 4k aligned   0000 010|0 0000
store z+(0*64+16)*4= z+64 where z is 4k aligned   0000 010|0 0000
load x+(0*64+24)*4  = x+96 where x is 4k aligned  0000 011|0 0000
load y+(0*64+24)*4  = y+96 where y is 4k aligned  0000 011|0 0000
store z+(0*64+24)*4 = z+96 where z is 4k aligned  0000 011|0 0000
load x+(0*64+32)*4 = x+128 where x is 4k aligned  0000 100|0 0000
load y+(0*64+32)*4 = y+128 where y is 4k aligned  0000 100|0 0000
store z+(0*64+32)*4= z+128 where z is 4k aligned  0000 100|0 0000
.
.
.
======
k=1
======
load x+(0*64+0)*4  = x+0 where x is 4k aligned       0000 000|0 0000
load y+(0*64+0)*4  = y+0 where y is 4k+64 aligned    0000 010|0 0000
store z+(0*64+0)*4 = z+0 where z is 4k+128 aligned   0000 100|0 0000
load x+(0*64+8)*4  = x+32 where x is 4k aligned      0000 001|0 0000
load y+(0*64+8)*4  = y+32 where y is 4k+64 aligned   0000 011|0 0000
store z+(0*64+8)*4 = z+32 where z is 4k+128 aligned  0000 101|0 0000
load x+(0*64+16)*4 = x+64 where x is 4k aligned      0000 010|0 0000
load y+(0*64+16)*4 = y+64 where y is 4k+64 aligned   0000 100|0 0000
store z+(0*64+16)*4= z+64 where z is 4k+128 aligned  0000 110|0 0000
load x+(0*64+24)*4  = x+96 where x is 4k aligned     0000 011|0 0000
load y+(0*64+24)*4  = y+96 where y is 4k+64 aligned  0000 101|0 0000
store z+(0*64+24)*4 = z+96 where z is 4k+128 aligned 0000 111|0 0000
load x+(0*64+32)*4 = x+128 where x is 4k aligned     0000 100|0 0000
load y+(0*64+32)*4 = y+128 where y is 4k+64 aligned  0000 110|0 0000
store z+(0*64+32)*4= z+128 where z is 4k+128 aligned 0001 000|0 0000
.
.
.
//sum.cpp
#包括
#包括
#包括
#包括
#定义计时器\u类型时钟\u实时
双时差(timespec开始、timespec结束)
{
时间样本温度;

如果((end.tv\u nsec-start.tv\u nsec)我认为
a
b
之间的差距并不重要。在
b
c
之间只留下一个差距之后,我在Haswell上得到了以下结果:

k   %
-----
1  48
2  48
3  48
4  48
5  46
6  53
7  59
8  67
9  73
10 81
11 85
12 87
13 87
...
0  86
由于已知Haswell没有银行冲突,唯一剩下的解释是内存地址之间的错误依赖性(您已经在Agner Fog的微体系结构手册中找到了正确的位置,准确地解释了这个问题)。存储组冲突和错误共享之间的区别在于,存储组冲突阻止在同一时钟周期内访问同一存储组两次,而错误共享阻止在将某些内容写入同一偏移量之后从4K内存中的某个偏移量读取(不仅在同一时钟周期内,而且在写入后的几个时钟周期内)


由于您的代码(对于
k=0
)在对同一个偏移量执行两次读取之后,才写入任何偏移量,并且在很长时间内不会读取,因此这种情况应被视为“最佳”,因此我将
k=0
放在表的末尾。对于
k=1
而言,您总是从最近被覆盖的偏移量中读取,这意味着错误共享,因此性能下降。随着
k
的增加,写入和读取之间的时间增加,CPU核心有更多机会通过所有内存层次结构传递写入的数据(这意味着读写的两个地址转换,更新缓存数据和标记,从缓存中获取数据,核心之间的数据同步,以及可能更多的内容)。
k=12
或24个时钟(在我的CPU上)足够让每个写入的数据块为后续的读取操作做好准备,因此从这个值开始,性能恢复正常。看起来与AMD上的20多个时钟没有太大区别(正如@Mystical所说).

TL;DR:对于
k
的某些值,出现过多的4K混叠情况,这是带宽降低的主要原因。在4K混叠中,负载被不必要地暂停,从而增加了有效负载延迟,并暂停了所有后续相关指令。这反过来会导致L1带宽降低h利用率。对于
k
的这些值,大多数4K混叠条件可以通过如下方式拆分循环来消除:

for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+  0,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 0), _mm256_load_ps(y1+64*i+  0)));
    _mm256_store_ps(z1+64*i+  8,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 8), _mm256_load_ps(y1+64*i+  8)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 16,_mm256_add_ps(_mm256_load_ps(x1+64*i+16), _mm256_load_ps(y1+64*i+ 16)));
    _mm256_store_ps(z1+64*i+ 24,_mm256_add_ps(_mm256_load_ps(x1+64*i+24), _mm256_load_ps(y1+64*i+ 24)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 32,_mm256_add_ps(_mm256_load_ps(x1+64*i+32), _mm256_load_ps(y1+64*i+ 32)));
    _mm256_store_ps(z1+64*i+ 40,_mm256_add_ps(_mm256_load_ps(x1+64*i+40), _mm256_load_ps(y1+64*i+ 40)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 48,_mm256_add_ps(_mm256_load_ps(x1+64*i+48), _mm256_load_ps(y1+64*i+ 48)));
    _mm256_store_ps(z1+64*i+ 56,_mm256_add_ps(_mm256_load_ps(x1+64*i+56), _mm256_load_ps(y1+64*i+ 56)));
}
请注意,当k=0时,无负载似乎满足4K别名的两个条件。另一方面,当k=1时,所有负载似乎都满足这些条件。然而,手动对所有迭代和
k
的所有值执行此操作非常繁琐。因此,我编写了一个程序,基本上生成内存访问的地址并计算总数对于不同的
k
值,出现4K别名的加载数。我面临的一个问题是,对于任何给定的加载,我们都不知道仍在存储缓冲区中的存储数(尚未提交).因此,我设计了模拟器,以便它可以对
k
的不同值使用不同的存储吞吐量,这似乎可以更好地反映真实处理器上实际发生的情况。可以找到代码

下图显示了模拟器产生的4K混叠案例的数量,与Haswell上使用
LD_BLOCKS\u PARTIAL.ADDRESS\u ALIAS
测得的数量进行了比较。我已针对
k
的每个值调整了模拟器中使用的存储吞吐量,以使两条曲线尽可能相似。第二个图显示了inver模拟器中使用的se存储吞吐量(总循环数除以总存储数)在Haswell上测量。请注意,k=0时的存储吞吐量并不重要,因为没有4K混叠。由于每个存储有两个负载,反向负载吞吐量是反向存储吞吐量的一半

显然,Haswell和模拟器上每个存储在存储缓冲区中的时间量是不同的,因此我需要使用不同的吞吐量使两条曲线相似。模拟器可以用来显示存储吞吐量如何影响4K别名的数量。如果存储吞吐量非常接近1c/存储,那么4K混叠情况的数量会小得多。4K混叠情况不会导致管道刷新,但可能会导致
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+  0,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 0), _mm256_load_ps(y1+64*i+  0)));
    _mm256_store_ps(z1+64*i+  8,_mm256_add_ps(_mm256_load_ps(x1+64*i+ 8), _mm256_load_ps(y1+64*i+  8)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 16,_mm256_add_ps(_mm256_load_ps(x1+64*i+16), _mm256_load_ps(y1+64*i+ 16)));
    _mm256_store_ps(z1+64*i+ 24,_mm256_add_ps(_mm256_load_ps(x1+64*i+24), _mm256_load_ps(y1+64*i+ 24)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 32,_mm256_add_ps(_mm256_load_ps(x1+64*i+32), _mm256_load_ps(y1+64*i+ 32)));
    _mm256_store_ps(z1+64*i+ 40,_mm256_add_ps(_mm256_load_ps(x1+64*i+40), _mm256_load_ps(y1+64*i+ 40)));
}
for(int i=0; i<n/64; i++) {
    _mm256_store_ps(z1+64*i+ 48,_mm256_add_ps(_mm256_load_ps(x1+64*i+48), _mm256_load_ps(y1+64*i+ 48)));
    _mm256_store_ps(z1+64*i+ 56,_mm256_add_ps(_mm256_load_ps(x1+64*i+56), _mm256_load_ps(y1+64*i+ 56)));
}
======
k=0
======
load x+(0*64+0)*4  = x+0 where x is 4k aligned    0000 000|0 0000
load y+(0*64+0)*4  = y+0 where y is 4k aligned    0000 000|0 0000
store z+(0*64+0)*4 = z+0 where z is 4k aligned    0000 000|0 0000
load x+(0*64+8)*4  = x+32 where x is 4k aligned   0000 001|0 0000
load y+(0*64+8)*4  = y+32 where y is 4k aligned   0000 001|0 0000
store z+(0*64+8)*4 = z+32 where z is 4k aligned   0000 001|0 0000
load x+(0*64+16)*4 = x+64 where x is 4k aligned   0000 010|0 0000
load y+(0*64+16)*4 = y+64 where y is 4k aligned   0000 010|0 0000
store z+(0*64+16)*4= z+64 where z is 4k aligned   0000 010|0 0000
load x+(0*64+24)*4  = x+96 where x is 4k aligned  0000 011|0 0000
load y+(0*64+24)*4  = y+96 where y is 4k aligned  0000 011|0 0000
store z+(0*64+24)*4 = z+96 where z is 4k aligned  0000 011|0 0000
load x+(0*64+32)*4 = x+128 where x is 4k aligned  0000 100|0 0000
load y+(0*64+32)*4 = y+128 where y is 4k aligned  0000 100|0 0000
store z+(0*64+32)*4= z+128 where z is 4k aligned  0000 100|0 0000
.
.
.
======
k=1
======
load x+(0*64+0)*4  = x+0 where x is 4k aligned       0000 000|0 0000
load y+(0*64+0)*4  = y+0 where y is 4k+64 aligned    0000 010|0 0000
store z+(0*64+0)*4 = z+0 where z is 4k+128 aligned   0000 100|0 0000
load x+(0*64+8)*4  = x+32 where x is 4k aligned      0000 001|0 0000
load y+(0*64+8)*4  = y+32 where y is 4k+64 aligned   0000 011|0 0000
store z+(0*64+8)*4 = z+32 where z is 4k+128 aligned  0000 101|0 0000
load x+(0*64+16)*4 = x+64 where x is 4k aligned      0000 010|0 0000
load y+(0*64+16)*4 = y+64 where y is 4k+64 aligned   0000 100|0 0000
store z+(0*64+16)*4= z+64 where z is 4k+128 aligned  0000 110|0 0000
load x+(0*64+24)*4  = x+96 where x is 4k aligned     0000 011|0 0000
load y+(0*64+24)*4  = y+96 where y is 4k+64 aligned  0000 101|0 0000
store z+(0*64+24)*4 = z+96 where z is 4k+128 aligned 0000 111|0 0000
load x+(0*64+32)*4 = x+128 where x is 4k aligned     0000 100|0 0000
load y+(0*64+32)*4 = y+128 where y is 4k+64 aligned  0000 110|0 0000
store z+(0*64+32)*4= z+128 where z is 4k+128 aligned 0001 000|0 0000
.
.
.
%define SIZE 64*64*2
%define K_   10

BITS 64
DEFAULT REL

GLOBAL main

EXTERN printf
EXTERN exit

section .data
align 4096
bufsrc1: times (SIZE+(64*K_)) db 1
bufsrc2: times (SIZE+(64*K_)) db 1
bufdest: times SIZE db 1

section .text
global _start
_start:
    mov rax, 1000000

.outer:
    mov rbp, SIZE/256
    lea rsi, [bufsrc1]
    lea rdi, [bufsrc2]
    lea r13, [bufdest]

.loop:
    vmovaps ymm1, [rsi]
    vaddps  ymm0, ymm1, [rdi]

    add rsi, 256
    add rdi, 256
    add r13, 256

    vmovaps[r13-256], ymm0

    vmovaps  ymm2, [rsi-224]
    vaddps   ymm0, ymm2, [rdi-224]
    vmovaps  [r13-224], ymm0

    vmovaps  ymm3, [rsi-192]
    vaddps   ymm0, ymm3, [rdi-192]
    vmovaps  [r13-192], ymm0

    vmovaps  ymm4, [rsi-160]
    vaddps   ymm0, ymm4, [rdi-160]
    vmovaps  [r13-160], ymm0

    vmovaps  ymm5, [rsi-128]
    vaddps   ymm0, ymm5, [rdi-128]
    vmovaps  [r13-128], ymm0

    vmovaps  ymm6, [rsi-96]
    vaddps   ymm0, ymm6, [rdi-96]
    vmovaps  [r13-96], ymm0

    vmovaps  ymm7, [rsi-64]
    vaddps   ymm0, ymm7, [rdi-64]
    vmovaps  [r13-64], ymm0

    vmovaps  ymm1, [rsi-32]
    vaddps   ymm0, ymm1, [rdi-32]
    vmovaps  [r13-32], ymm0

    dec rbp
    jg .loop

    dec rax
    jg .outer

    xor edi,edi
    mov eax,231
    syscall