C++ 两个阵列/映像相乘时的多线程性能-英特尔IPP

C++ 两个阵列/映像相乘时的多线程性能-英特尔IPP,c++,multithreading,openmp,intel-ipp,C++,Multithreading,Openmp,Intel Ipp,我正在使用Intel IPP对2个图像(阵列)进行乘法运算。 我使用的是Intel Composer 2015 Update 6附带的Intel IPP 8.2 我创建了一个简单的函数来倍增太大的图像(整个项目附在后面,见下文)。 我想看看使用“英特尔IPP多线程库”的好处 这是一个简单的项目(我还附上了完整的项目表单Visual Studio): #包括“ippi.h” #包括“ippcore.h” #包括“ipps.h” #包括“ippcv.h” #包括“ippcc.h” #包括“ippvm

我正在使用Intel IPP对2个图像(阵列)进行乘法运算。
我使用的是Intel Composer 2015 Update 6附带的Intel IPP 8.2

我创建了一个简单的函数来倍增太大的图像(整个项目附在后面,见下文)。
我想看看使用“英特尔IPP多线程库”的好处

这是一个简单的项目(我还附上了完整的项目表单Visual Studio):

#包括“ippi.h”
#包括“ippcore.h”
#包括“ipps.h”
#包括“ippcv.h”
#包括“ippcc.h”
#包括“ippvm.h”
#包括
#包括
使用名称空间std;
const int height=6000;
常数整数宽度=6000;
Ipp32f mInput_图像[1*宽度*高度];
Ipp32f mOutput_图像[1*宽度*高度]={0};
int main()
{
iPisize size={宽度,高度};
双启动=时钟();
对于(int i=0;i<200;i++)
ippiMul_32f_C1R(mInput_图像,6000*4,mInput_图像,6000*4,mOutput_图像,6000*4,大小);
双端=时钟();
双倍率=(结束-开始)/静态(时钟/秒);

根据我自己的一些研究,看起来你的CPU缓存总量大约是8MB。6000*4/4(6000个浮点被分成4个块)是6MB。乘以2(输入和输出),你就在缓存之外了

我还没有对此进行测试,但是增加块的数量应该会提高性能。尝试从8开始(您的CPU将超线程扩展到8个虚拟内核)

目前,OpenMP上生成的每个不同进程都存在缓存冲突,必须从主内存(重新)加载。减小块的大小可以帮助解决此问题。具有不同的CAHCE将有效地增加缓存的大小,但这似乎不是一个选项


如果您只是为了证明这一点,您可能希望通过在图形卡上运行它来测试这一点。不过,这可能更难正确实现。

如果您在启用HyperRead的情况下运行,您应该尝试openmp版本的ipp,每个核心1个线程,如果ipp没有自动执行,则应设置omp_places=coresse Cilk________________________________。
您可以尝试一个足够大的测试用例来跨越多个4kb页面。然后其他因素会起作用。理想情况下,ipp会将线程放在不同的页面上工作。在Linux(或Mac?)上透明的巨大页面应该开始发挥作用。在Windows上,haswell CPU引入了硬件页面预取,这应该会减少但不会消除thp的重要性。

您的图像总共占用200 MB(2 x 5000 x 5000 x 4字节)。因此,每个块由50 MB的数据组成。这是CPU三级缓存大小的6倍多(请参阅)。每个AVX矢量乘法操作256位数据,即半个缓存线,即每个矢量指令消耗一个缓存线(每个参数使用半个缓存线)。Haswell上的矢量乘法延迟为5个周期,FPU每个周期可以退出两个这样的指令(请参阅).i7-4770K的内存总线额定值为25.6 GB/s(理论值最大值!)或者每秒不超过4.3亿条缓存线。CPU的标称速度为3.5 GHz。AVX部分的时钟稍低,比如说3.1 GHz。在该速度下,每秒需要多出一个数量级的缓存线才能完全为AVX引擎供电

在这些情况下,向量化代码的单个线程几乎会完全饱和CPU的内存总线。添加第二个线程可能会导致非常轻微的改进。添加更多线程只会导致冲突和增加开销。加快此类计算的唯一方法是增加内存带宽:

  • 在具有更多内存控制器的NUMA系统上运行,因此具有更高的聚合内存带宽,例如多插槽服务器板
  • 切换到具有更高内存带宽的不同体系结构,例如Intel Xeon Phi或GPGPU

您使用的编译选项是什么?您应该使用
/O2/openmp
/O2/openmp/arch:AVX2
。这并不重要,因为英特尔IPP是通过汇编和CPU调度构建的。但它是/O2/openmp。谢谢。嗨,我不确定为什么没有增益。这是内存瓶颈吗?我如何验证它?嗨,但我们可以现在我们可以更好地实现多线程矩阵乘法。这样做的策略是什么?此外,除了理论计算之外,VS中是否有一个工具可以向我显示内存是否饱和?谢谢你的回答。使用密集矩阵乘法,每单位输出数据和d每个输入元素用于计算多个输出元素。这允许使用缓存阻塞等技术。在您的情况下,数据基本上以线性方式流入和流出CPU,缓存阻塞无法使用。我指的是数组乘法(错误写入矩阵).此外,有没有一种工具可以分析这些东西?你可能会得到它(某种程度上)如果您放弃IPP并自己实现一个简单的乘法循环,那么可以扩展到2-3个线程,然后在禁用所有优化的情况下以32位模式和x87数学进行编译,然后大量降低CPU时钟。至于分析,我已经向您介绍了理论分析。有关更多详细信息,请查看屋顶线性能模型。CPU具有可以用适当的工具读取的e计数器。我所知道的唯一适用于Windows的工具是英特尔VTune(我通常在Linux上使用HPC,并且该操作系统有很多工具,其中许多是开源的)。看来V-Tune就是我要找的。非常好的分析,非常感谢!您似乎对这个主题有很多了解。
#include "ippi.h"
#include "ippcore.h"
#include "ipps.h"
#include "ippcv.h"
#include "ippcc.h"
#include "ippvm.h"

#include <ctime>
#include <iostream>

using namespace std;

const int height = 6000;
const int width  = 6000;
Ipp32f mInput_image [1 * width * height];
Ipp32f mOutput_image[1 * width * height] = {0};

int main()
{
    IppiSize size = {width, height};

    double start = clock();

    for (int i = 0; i < 200; i++)
        ippiMul_32f_C1R(mInput_image, 6000 * 4, mInput_image, 6000 * 4, mOutput_image, 6000 * 4, size); 

    double end = clock();
    double douration = (end - start) / static_cast<double>(CLOCKS_PER_SEC);

    cout << douration << endl;
    cin.get();

    return 0;
}
#include "ippi.h"
#include "ippcore.h"
#include "ipps.h"
#include "ippcv.h"
#include "ippcc.h"
#include "ippvm.h"

#include <ctime>
#include <iostream>

using namespace std;

#include <omp.h>

const int height = 5000;
const int width  = 5000;
Ipp32f mInput_image [1 * width * height];
Ipp32f mOutput_image[1 * width * height] = {0};

int main()
{
    IppiSize size = {width, height};

    double start = clock();

    IppiSize blockSize = {width, height / 4};

    const int NUM_BLOCK = 4;
    omp_set_num_threads(NUM_BLOCK);

    Ipp32f*  in;
    Ipp32f*  out;

    //  ippiMul_32f_C1R(mInput_image, width * 4, mInput_image, width * 4, mOutput_image, width * 4, size);

    #pragma omp parallel            \
    shared(mInput_image, mOutput_image, blockSize) \
    private(in, out)
    {
        int id   = omp_get_thread_num();
        int step = blockSize.width * blockSize.height * id;
        in       = mInput_image  + step;
        out      = mOutput_image + step;
        ippiMul_32f_C1R(in, width * 4, in, width * 4, out, width * 4, blockSize);
    }

    double end = clock();
    double douration = (end - start) / static_cast<double>(CLOCKS_PER_SEC);

    cout << douration << endl;
    cin.get();

    return 0;
}