在Visual Studio 2017中启用Open MP支持会减慢代码速度

时间:2018-11-14 12:04:03

标签: visual-studio-2017 openmp eigen

我正在尝试使用OpenMP来加快我的神经网络计算代码。在使用Visual Studio 2017时,需要在属性表中启用OpenMP支持。但是,这样做之后,即使我没有在代码中包含任何#pragma omp,部分代码的速度也会降低5倍左右。

我隔离了这些部分,发现此特定功能导致了问题:

void foo(Eigen::Matrix<float,3,Eigen::Dynamic> inputPts)
{
    std::vector<Eigen::MatrixXf> activation;
    activation.reserve(layerNo);
    activation.push_back(inputPts);

    int inputNo = inputPts.cols();

    for (int i = 0; i < layerNo - 2; i++)
        activation.push_back(((weights[i]*activation[i]).colwise()+bias[i]).array().tanh());

    activation.push_back(((weights[layerNo - 2]*activation[layerNo - 2]).colwise()+bias[layerNo - 2]));

    val = activation[layerNo - 1]/scalingFactor;

    std::vector<Eigen::MatrixXf> delta;
    delta.reserve(layerNo);

    Eigen::Matrix<float, 1, Eigen::Dynamic> seed;
    seed.setOnes(1, inputNo);
    delta.push_back(seed);

    for (int i = layerNo - 2; i >= 1; i--)
    {
        Eigen::Matrix<float,Eigen::Dynamic,Eigen::Dynamic>
                d_temp = weights[i].transpose()*delta[layerNo - 2 - i],
                d_temp2 = 1 - activation[i].array().square(),
                deltaLayer = d_temp.cwiseProduct(d_temp2);

        delta.push_back(deltaLayer);
    }

    grad = weights[0].transpose()*delta[layerNo - 2];
}

两个for循环是一个显着减慢的循环(从〜3ms到〜20ms)。奇怪的是,尽管此函数在程序中被多次调用,但仅其中一些会受到影响。

我已包含头文件<omp.h>。我不确定这是否归因于Eigen库,该库随处可见。我尝试按照official site中的建议定义EIGEN_DONT_PARALLELIZE并调用Eigen::initParallel(),但这无济于事。

奇怪的是,我什至根本不包含任何parallel pragma,处理OpenMP函数应该没有任何开销吗?为什么它仍然放慢速度?

1 个答案:

答案 0 :(得分:1)

如果启用了OpenMP,则默认情况下,Eigen的矩阵矩阵产品是多线程的。问题可能是以下原因的组合:

  1. 您的CPU是超线程的,例如,您有4个物理内核能够运行8个线程。
  2. OpenMP不允许知道物理内核的数量,因此Eigen将启动8个线程。
  3. 本征的矩阵矩阵产品内核已完全优化,并利用了将近100%的CPU容量。因此,在单个内核上没有运行两个这样的线程的空间,并且性能大大下降(缓存污染)。

因此,解决方案是例如通过设置OMP_NUM_THREADS环境变量,将OpenMP线程的数量限制为物理内核的数量。您还可以通过在编译时定义宏EIGEN_DONT_PARALLELIZE来禁用Eigen的多线程。

doc中的更多信息。

有关超线程如何降低性能的更多详细信息: 使用超线程,您可以在单个内核上以交错方式运行两个线程。他们交替执行每条指令。如果您的线程使用的CPU资源不少于一半(就计算而言),那将是一个胜利,因为您将利用更多的计算单元。但是,如果单个线程已经使用了100%的计算单元(如优化矩阵矩阵产品的情况),则由于1)管理两个线程的自然开销和2)因为L1而导致性能下降缓存现在由两个不同的任务共享。矩阵矩阵内核在设计时考虑到了精确的L1容量。有两个线程,您的L1缓存几乎无效。这意味着您没有访问大多数时间的非常快的L1高速缓存,而是访问了慢得多的L2高速缓存,从而导致性能大幅下降。与Linux和Windows不同,在OSX上我看不到这种性能下降,这很可能是因为如果CPU太忙了,系统便可以取消第二线程的调度。