【问题标题】:Why is VC++ matrix times vector faster with openMP than async?为什么使用 openMP 的 VC++ 矩阵时间向量比异步更快?
【发布时间】:2018-04-05 11:55:47
【问题描述】:

我以两种方式对向量乘以矩阵进行编码,一种使用 openMP,另一种使用 std::async。我预计性能几乎相同。 OpenMP 在第一次调用时很慢,可能是因为它推迟了创建线程池。之后,异步版本始终慢 40%。 (我有英特尔酷睿 i5,即 4 核。)

有什么关系? VC++ 不使用线程池进行异步吗?我在做傻事吗? (很可能。)我认为对输出向量的访问间隔足够大,以避免错误共享。

#include "stdafx.h"
# include <iostream>
# include <vector>
# include <omp.h>
# include <ctime>
#include <numeric>
#include <thread>
#include <chrono>
#include <future>

// Matrix multiplication of vector using omp
template<class Mat, class Vec>
void mult_mat_vec_omp
(const Mat &mat, const Vec &inp, Vec &out) {
    const int steps = static_cast<int>(std::size(mat));
    using std::begin; using std::end;
    auto N = std::thread::hardware_concurrency();
    omp_set_num_threads(N); 
#pragma omp parallel for 
    for (int i=0; i < steps; ++i) {
        out[i] = std::inner_product(begin(mat[i]), end(mat[i]), begin(inp), 0.0);
    }
}


// Matrix multiplication of vector using async
template<class Mat, class Vec>
void mult_mat_vec_async
(const Mat &mat, const Vec &inp, Vec &out) {
    using std::begin; using std::end;
    auto N = std::thread::hardware_concurrency();
    typedef decltype(N) usigned;
    const unsigned steps = static_cast<unsigned>(std::size(mat));
    auto f = [&](unsigned id) {
    for (unsigned i=id; i < steps; i+= N) {
            out[i] = std::inner_product(begin(mat[i]), end(mat[i]), begin(inp), 0.0);
        }
    };
    std::vector<std::future<void>> threads;
    for (unsigned i = 1; i<N; ++i) {
        threads.push_back(std::async(std::launch::async, f, i));
    }
    f(0);
    for (auto &x: threads) {
        x.get();
    }
}


double test() {
    using std::vector;
    using clock=std::chrono::high_resolution_clock;
    vector<double> a;
    vector<double> b;
    vector<double> c;
    vector<vector<double>> mat;
    vector<double> v;
    int rows = 350;
    int cols = 350;
    for (int i = 0; i< cols; ++i) {
        a.push_back(i/10.0);
        b.push_back(-999);
        c.push_back(8888);
    }
    for (int i=0; i<rows; ++i) {

        v.clear();
        for (int j=0; j<cols; ++j) {
            v.push_back (((i+.5)*j)/100.0);
        }
        mat.push_back(v);
    }
    clock::time_point start = clock::now();
    int N = 10000;
    for (int i=0; i< N/10; ++i) { 
    mult_mat_vec_omp(mat, a, b) ;
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);
    mult_mat_vec_omp(mat, a, b);

    };
    long long duration =  
std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()-start).count();
    start = clock::now();
    size_t cutoff = 0; // 2*rows*cols;
    for (int i=0; i< N/10; ++i) { 
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);
    mult_mat_vec_async(mat, a, c);

    };
 long long duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()-start).count();
    //cout << mat[0][5] << " " << b[0] << " " << c[0] << endl;
    bool good = (b==c);
    std::cout << duration*(1.0/N) << ' ' << duration2*(1.0/N) << " " << good << std::endl;
    return 0;
}

int main ()
{
    for(int i=0; i<15; ++i) test();
    return 0;
}

【问题讨论】:

  • 基准测试持续多长时间?秒?毫秒?
  • 每次迭代的秒数(发布模式)。如果你必须赶公共汽车,如何减少这种情况应该很明显。减少 N=10000。
  • 一方面,std::async 版本中的内存访问模式非常糟糕。每个线程都跨步N。在 OpenMP 版本中,您让 OpenMP 处理工作分配。它可能使用块分布而不是步幅分布。
  • 因为缓存未命中而很糟糕? N 只有 4。
  • 我不确定这是否会有所不同,但这意味着每个线程接触的 cachlines 比它需要的多 4 倍。另一种可能性是您在写入out[i] 时得到错误共享。它们在线程之间交错,因此很有可能发生冲突。

标签: c++ multithreading visual-c++ openmp stdasync


【解决方案1】:

在 Intel Core i7-2600 上,禁用 HT,使用 gcc 7.2 / Linux,数字略有不同,异步版本慢约 10%。

现在关于缓存效率和虚假共享的讨论正朝着正确的方向发展。您应该尝试通过同一线程访问连续元素,至少达到缓存行的大小(例如 64 字节)。对于读取,您只需通过使用缓存/数据局部性来提高内存访问效率 - 对于写入它更糟糕,因为错误共享会在内核之间的缓存线周围反弹。然而,重要的是要认识到这与实际的数据访问无关——这发生在std::inner_product 中,并且对于两个版本都是相同的。如果实际数据访问采用这种线程交错的模式,性能会比 40% 的折扣差很多。

现在很容易避免并测试它是否有帮助:

const unsigned steps = static_cast<unsigned>(std::size(mat));
auto f = [&](unsigned id) {
    const auto chunk_size = 1 + ((steps - 1) / N);
    const auto max = std::min(chunk_size * (id + 1), steps);
    for (unsigned i = chunk_size * id; i < max; i++)
    {
        out[i] = std::inner_product(begin(mat[i]), end(mat[i]), begin(inp), 0.0);
    }
};

在我的配置中消除了版本之间的所有性能差异。

如果您仍然发现系统性能存在差异,我建议您使用合适的性能分析工具。我不熟悉您的生态系统,因此无法提供任何建议 - 但重要的是不要猜测性能。

请注意,std::vector&lt;std::vector&lt;&gt;&gt; 不是高性能数据访问/矩阵乘法的良好数据结构。您不会接近使用矩阵连续内存的高度优化库的性能。

【讨论】:

  • 非常感谢。在我的机器上,以这种方式从跨步更改为分块提高了 10%。异步版本仍然比 omp 慢 30%。很奇怪。 omp 版本在幕后做了什么魔法?
  • 我将测试例程中的“mat”从 > 更改为 double mat[rows][cols].. 我很高兴模板可以正常工作,但我看到了没有速度提升。
猜你喜欢
  • 2017-01-19
  • 1970-01-01
  • 1970-01-01
  • 2013-11-13
  • 1970-01-01
  • 2012-05-13
  • 1970-01-01
  • 2017-06-07
  • 2018-12-05
相关资源
最近更新 更多