Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.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 用于矩阵乘法的最大触发器Intel/AMD CPU_Performance_Intel_Sse_Matrix Multiplication_Eigen - Fatal编程技术网

Performance 用于矩阵乘法的最大触发器Intel/AMD CPU

Performance 用于矩阵乘法的最大触发器Intel/AMD CPU,performance,intel,sse,matrix-multiplication,eigen,Performance,Intel,Sse,Matrix Multiplication,Eigen,我估计英特尔CPU的最大触发器数的公式是 Max SP FLOPs/s = frequencey * 4 SSE(8AVX) * 2 (MAC) * number of cores (not HW threads) Max DP FLOPs/s = 0.5 * Max SP FLOPs/s 我所说的MAC是指CPU可以同时进行一次SSE(AVX)乘法和加法。在我使用的系统上,负载下的最大频率是2.66GHz。它只有SSE,核心数(不是硬件线程)是4。这就给出了:最大SP FLOPs/s=85.

我估计英特尔CPU的最大触发器数的公式是

Max SP FLOPs/s = frequencey * 4 SSE(8AVX) * 2 (MAC) * number of cores (not HW threads)
Max DP FLOPs/s = 0.5 * Max SP FLOPs/s
我所说的MAC是指CPU可以同时进行一次SSE(AVX)乘法和加法。在我使用的系统上,负载下的最大频率是2.66GHz。它只有SSE,核心数(不是硬件线程)是4。这就给出了:最大SP FLOPs/s=85.12 GFLOPs/s

矩阵乘法的失败次数接近
2*n*m*k
。对于n=1000的方阵,这是2*10E9次浮点运算(20亿次浮点运算)。一旦我知道了时间,我就可以估计失败次数了

但是,对于我自己的代码,我能得到的最好结果是大约40 SP GFLOPs/s,例如,n=1000。我得到了和艾根差不多的结果。这大约是45%的效率。我的最大值计算错了吗?对于大型密集矩阵乘法,英特尔CPU的最佳效率是什么?有人写过这篇文章吗

我知道在GPU上,效率可以超过60%。

编辑: 对于n=500,我得到了类似的结果,这很容易适应我的系统的12MB三级缓存,因此缓存似乎不是限制因素(尽管也许我可以更有效地使用它)

编辑2: Eigen基准测试表明它和MKL(对于SSE)一样好。他们使用Intel(R)Core(TM)2四处理器Q9400@2.66GHz。So 2.66*2(DP SSE)*2 MAC*4芯=42.25 DP GFLOPs/s。你可以从图上看到,他们都得到了不到20。像我这样45%的人。

编辑3: 这是我给任何关心我的人的代码。我可以得到比这稍微好一点的结果,但不会好很多。我正在为SEE/AVX使用Agner Fog的vectorclass。并将Vec8f设置为float8,将Vec4d设置为double4

//SGEMM and AVX call MM_tile<float, float8>(nthreads, a, b, c, n, m, k);
template <typename ftype, typename floatn>
void GEMM_tile(const int nthreads, const ftype*A , const ftype* B, ftype* C, const int N, const int M, const int K) {       
    for(int i=0; i<N; i++) {
       for(int j=0; j<K; j++) {
           C[K*i + j] = 0;
       }
    }   
    const int nc = 32;
    const int kc = 32;
    const int mc = 32;
    omp_set_num_threads(nthreads);
    #pragma omp parallel for if(nthreads>1)
    for(int ii=0; ii<N; ii+=nc) {
        for(int jj=0; jj<K; jj+=kc)
            for(int ll=0; ll<M; ll+=mc) {
                const int nb = min(N-ii, nc);
                const int kb = min(K-jj, kc);
                const int mb = min(M-ll, mc);
                MM_block<ftype, floatn>(nb, mb, kb, &A[M*ii+ll], N, &B[K*ll+jj], K, &C[K*ii+jj], K );
            }
     }
}

template <typename ftype, typename floatn>
void MM_block(int n, int m, int k, const ftype *a, const int stridea, 
                                   const ftype *b, const int strideb,
                                   ftype *c, const int stridec ) {
    const int vec_size = sizeof(floatn)/sizeof(ftype);
    for(int i=0; i<n; i+=4) {
        for(int j=0; j<k; j+=vec_size) {
            Dot4x4_vec_block<ftype, floatn>(m, &a[strideb*i], &b[j], &c[stridec*i + j], stridea, strideb, stridec);
    }
}

template <typename ftype, typename floatn>
inline void Dot4x4_vec_block(const int n, const ftype *a, const ftype *b, ftype *c, const int stridea, const int strideb, const int stridec) {
    floatn tmp0, tmp1, tmp2, tmp3;
    load(tmp0, &c[stridec*0]);
    load(tmp1, &c[stridec*1]);
    load(tmp2, &c[stridec*2]);
    load(tmp3, &c[stridec*3]);

    ftype *a0_ptr = (ftype*)&a[stridea*0];
    ftype *a1_ptr = (ftype*)&a[stridea*1];
    ftype *a2_ptr = (ftype*)&a[stridea*2];
    ftype *a3_ptr = (ftype*)&a[stridea*3];
    for(int i=0; i<n; i++) {
        floatn breg = floatn().load(&b[i*strideb + 0]);

        floatn areg0 = *a0_ptr++;
        floatn areg1 = *a1_ptr++;
        floatn areg2 = *a2_ptr++;
        floatn areg3 = *a3_ptr++;

        tmp0 += areg0 * breg;
        tmp1 += areg1 * breg;
        tmp2 += areg2 * breg;
        tmp3 += areg3 * breg;
}
    tmp0.store(&c[stridec*0]);
    tmp1.store(&c[stridec*1]);
    tmp2.store(&c[stridec*2]);
    tmp3.store(&c[stridec*3]);
}
//SGEMM和AVX调用MM_tile(n个线程,a、b、c、n、m、k);
模板
无效GEMM_tile(常量int N读取,常量ftype*A,常量ftype*B,ftype*C,常量int N,常量int M,常量int K){

对于(int i=0;i通常,处理吞吐量的限制因素是内存带宽,特别是在您的工作集不适合CPU缓存的情况下(您的1000×1000浮点矩阵将占用~4 MB,而您的CPU可能有2 MB的三级缓存)。在这种情况下,算法的结构会对其执行方式产生很大的影响,但通常会在某个点撞到墙,因为您正在等待来自内存层次结构中某个更高级别的值,因此无法加快速度


此外,你的理论数字假设你有足够的指令,没有数据依赖性,可以让所有执行单元在每个周期都有任务。这在实践中可能很难做到。我不确定一般矩阵乘法的最佳吞吐量是多少,但请查看你可以做些什么来解决这个问题的信息最大限度地提高指令吞吐量。

我使用循环平铺/阻塞来处理缓存。我听说过对L1、L2、L3使用多级阻塞。我还没有这样做。我使用循环展开来处理数据依赖性。无论如何,我的结果和Eigen一样好,我认为这是SSE(不是AVX)的最先进技术。因此,如果我的代码效率低下,那么Eigen的代码也是如此。在我看来,您的代码的效率并没有明显低于预期的效率。听起来您做了正确的事情,以确保您的算法尽可能对内存友好。正确的平铺矩阵乘法算法永远不会受到内存带宽的限制(关于现代体系结构,包括x86)。性能的限制因素是原始计算吞吐量。谢谢,这就是我的想法。我认为这是因为矩阵乘法是一个n^3阶,所以它处理数据的时间比读取数据的时间要长得多,所以限制因素应该是处理。我稍后会写更多内容,但好的BLAS实现可以逐渐实现约90%的pe现代x86硬件上的ak。MKL和GotoBLAS都是很好的参考点。超过60%需要仔细调整内部循环,并考虑到指令延迟和端口利用率。它通常需要编写汇编程序,除非您碰巧发现了编译器处理得非常好的固有习惯用法。谢谢,90%是w这是我所希望的。我在Eigen vs MKL上添加了一些文字。这和他们声称的MKL一样好(对于SSE)。有趣的是,我的公式预测超过40 DP GLOPs/s,它们都不到20。也许我的公式偏离了2倍?我找到了一些代码来分析MKL。他们有一张i7-2600K的图表。他们得到90 DP GFLOPs/s。我在家里2600K的时候得到50 DP GFLPS/s。这告诉我我的公式是正确的,我的代码是正确的(和本征的)是低效的。