C++ 将向量向量转换为具有相反存储顺序的单个连续向量的更快方法

C++ 将向量向量转换为具有相反存储顺序的单个连续向量的更快方法,c++,performance,caching,vector,C++,Performance,Caching,Vector,我有一个std::vector,我正试图尽快将其转换为单个连续向量。我的向量的形状大约为4000x50 问题是,有时我需要以列主要连续顺序输出向量(只是将2d输入向量的内部向量串联起来),有时我需要以行主要连续顺序输出向量,实际上需要转置 我发现NaiveFor循环转换为列主向量非常快: auto to_dense_column_major_naive(std::vector<std::vector<double>> const & vec) ->

我有一个
std::vector
,我正试图尽快将其转换为单个连续向量。我的向量的形状大约为
4000x50

问题是,有时我需要以列主要连续顺序输出向量(只是将2d输入向量的内部向量串联起来),有时我需要以行主要连续顺序输出向量,实际上需要转置

我发现NaiveFor循环转换为列主向量非常快:

auto to_dense_column_major_naive(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    for (size_t i = 0; i < n_col; ++i)
        for (size_t j = 0; j < n_row; ++j)
            out_vec[i * n_row + j] = vec[i][j];
    return out_vec;
}
auto to_density_column_major_naive(std::vector const&vec)
->向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
对于(大小i=0;i
但很明显,类似的方法对于行转换非常慢,因为所有的缓存未命中。因此,对于行转换,我认为使用阻塞策略来提升缓存局部性可能是我的最佳选择:

auto to_dense_row_major_blocking(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    size_t block_side = 8;

    for (size_t l = 0; l < n_col; l += block_side) {
        for (size_t k = 0; k < n_row; k += block_side) {
            for (size_t j = l; j < l + block_side && j < n_col; ++j) {
                auto const &column = vec[j];
                for (size_t i = k; i < k + block_side && i < n_row; ++i)
                    out_vec[i * n_col + j] = column[i];
            }
        }
    }
    return out_vec;
}
auto to_density_row_major_blocking(标准::向量常量和向量)
->向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
块体侧尺寸=8;
用于(尺寸l=0;l
这比行主转换的朴素循环快得多,但在输入大小上仍然比朴素列主循环慢近一个数量级

我的问题是,有没有更快的方法将双精度向量的(列主)向量转换为单个连续行主向量?我正在努力思考这段代码的速度限制应该是多少,因此我质疑我是否遗漏了一些明显的东西。我的假设是,阻塞将给我一个更大的加速比它似乎真的给


该图表是使用QuickBench生成的(并在我的机器上用GBench进行了局部验证),代码如下:(Clang7,C++20,-O3)

auto to_density_column_major_naive(std::vector const&vec)
->向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
对于(大小i=0;i向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
对于(大小i=0;i向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
块体侧尺寸=8;
用于(尺寸l=0;l向量
{
自动n_col=vec.size();
自动n_行=向量[0]。大小();
std::向量输出向量(n列*n行);
块体侧尺寸=8;
用于(尺寸l=0;lstd::vector
{
std::vector vec(50,std::vector(4000));
标准:mt19937梅森尼{2019};
标准:均匀实分布区(-10001000);
用于(自动和矢量:矢量)
用于(自动和val:vec)
val=距离(梅森);
返回向量;
}
静态测试(基准测试::状态和状态){
//未测量循环之前的代码
自动向量=生成向量();
用于(自动:状态){
基准测试::DonoOptimize(to_densite_column_major_naive(vecvec));
}
}
基准(专业);
静态测试(基准::状态和状态){
//未测量循环之前的代码
自动向量=生成向量();
用于(自动:状态){
基准测试::DonoOptimize(to_densite_row_major_naive(vecvec));
}
}
基准(专业);
静态void BlockingRowMajor(基准::状态和状态){
//未测量循环之前的代码
自动向量=生成向量();
用于(自动:状态){
基准测试:不优化(到密集行主要阻塞(vecvec));
}
}
基准(BlockingRowMajor);
静态void BlockingColumnMajor(基准::状态和状态){
//未测量循环之前的代码
自动向量=生成向量();
用于(自动:状态){
基准测试:不优化(到密集列、主要阻塞(vecvec));
}
}
基准(主要);

首先,当某些东西被限定为“显然”时,我会退缩。这个词常被用来掩盖一个人在推理中的缺点

但很明显,类似的方法对于行转换非常慢,因为
auto to_dense_column_major_naive(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    for (size_t i = 0; i < n_col; ++i)
        for (size_t j = 0; j < n_row; ++j)
            out_vec[i * n_row + j] = vec[i][j];
    return out_vec;
}

auto to_dense_row_major_naive(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    for (size_t i = 0; i < n_col; ++i)
        for (size_t j = 0; j < n_row; ++j)
            out_vec[j * n_col + i] = vec[i][j];
    return out_vec;
}

auto to_dense_row_major_blocking(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    size_t block_side = 8;

    for (size_t l = 0; l < n_col; l += block_side) {
        for (size_t k = 0; k < n_row; k += block_side) {
            for (size_t j = l; j < l + block_side && j < n_col; ++j) {
                auto const &column = vec[j];
                for (size_t i = k; i < k + block_side && i < n_row; ++i)
                    out_vec[i * n_col + j] = column[i];
            }
        }
    }
    return out_vec;
}

auto to_dense_column_major_blocking(std::vector<std::vector<double>> const & vec)
    -> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    size_t block_side = 8;

    for (size_t l = 0; l < n_col; l += block_side) {
        for (size_t k = 0; k < n_row; k += block_side) {
            for (size_t j = l; j < l + block_side && j < n_col; ++j) {
                auto const &column = vec[j];
                for (size_t i = k; i < k + block_side && i < n_row; ++i)
                    out_vec[j * n_row + i] = column[i];
            }
        }
    }
    return out_vec;
}

auto make_vecvec() -> std::vector<std::vector<double>>
{
  std::vector<std::vector<double>> vecvec(50, std::vector<double>(4000));
  std::mt19937 mersenne {2019};
  std::uniform_real_distribution<double> dist(-1000, 1000);
  for (auto &vec: vecvec)
   for (auto &val: vec)
       val = dist(mersenne);
  return vecvec;
}

static void NaiveColumnMajor(benchmark::State& state) {
  // Code before the loop is not measured

  auto vecvec = make_vecvec();
  for (auto _ : state) {
    benchmark::DoNotOptimize(to_dense_column_major_naive(vecvec));
  }
}
BENCHMARK(NaiveColumnMajor);

static void NaiveRowMajor(benchmark::State& state) {
  // Code before the loop is not measured

  auto vecvec = make_vecvec();
  for (auto _ : state) {
    benchmark::DoNotOptimize(to_dense_row_major_naive(vecvec));
  }
}
BENCHMARK(NaiveRowMajor);

static void BlockingRowMajor(benchmark::State& state) {
  // Code before the loop is not measured

  auto vecvec = make_vecvec();
  for (auto _ : state) {
    benchmark::DoNotOptimize(to_dense_row_major_blocking(vecvec));
  }
}
BENCHMARK(BlockingRowMajor);

static void BlockingColumnMajor(benchmark::State& state) {
  // Code before the loop is not measured

  auto vecvec = make_vecvec();
  for (auto _ : state) {
    benchmark::DoNotOptimize(to_dense_column_major_blocking(vecvec));
  }
}
BENCHMARK(BlockingColumnMajor);
for (size_t i = 0; i < n_col; ++i)
    for (size_t j = 0; j < n_row; ++j)
        out_vec[j * n_col + i] = vec[i][j];
for (size_t j = 0; j < n_row; ++j)
    for (size_t i = 0; i < n_col; ++i)
        out_vec[j * n_col + i] = vec[i][j];
for (size_t l = 0; l < n_col; l += block_side)
    for (size_t i = 0; i < n_row; ++i)
        for (size_t j = l; j < l + block_side && j < n_col; ++j)
            out_vec[i * n_col + j] = vec[j][i];
#include <ppl.h>

auto ppl_to_dense_column_major_naive(std::vector<std::vector<double>> const & vec)
-> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);

    size_t vecLen = out_vec.size();
    concurrency::parallel_for(size_t(0), vecLen, [&](size_t i)
    {
        size_t row = i / n_row;
        size_t column = i % n_row;

        out_vec[i] = vec[row][column];
    });

    return out_vec;
}

auto ppl_to_dense_row_major_naive(std::vector<std::vector<double>> const & vec)
-> std::vector<double>
{
    auto n_col = vec.size();
    auto n_row = vec[0].size();
    std::vector<double> out_vec(n_col * n_row);
    size_t vecLen = out_vec.size();


    concurrency::parallel_for(size_t(0), vecLen, [&](size_t i)
    {
        size_t column = i / n_col;
        size_t row = i % n_col;

        out_vec[i] = vec[row][column];
    });

    return out_vec;
}
template< class _Fn, class ... Args >
auto callFncWithPerformance( std::string strFnName,  _Fn toCall, Args&& ...args )
{
    auto start = std::chrono::high_resolution_clock::now();
    auto toRet = toCall( std::forward<Args>(args)... );
    auto end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> diff = end - start;

    std::cout << strFnName << ": " << diff.count() << " s" << std::endl;

    return toRet;
}

template< class _Fn, class ... Args >
auto second_callFncWithPerformance(_Fn toCall, Args&& ...args)
{
    std::string strFnName(typeid(toCall).name());

    auto start = std::chrono::high_resolution_clock::now();
    auto toRet = toCall(std::forward<Args>(args)...);
    auto end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> diff = end - start;

    std::cout << strFnName << ": " << diff.count() << " s";

    return toRet;
}


#define MAKEVEC( FN, ... ) callFncWithPerformance( std::string( #FN ) , FN  , __VA_ARGS__ )


int main()
{
    //prepare vector
    auto vec = make_vecvec();

    std::vector< double > vecs[]
    {
        std::vector<double>(MAKEVEC(to_dense_column_major_naive, vec)),
        std::vector<double>(MAKEVEC(to_dense_row_major_naive, vec)),
        std::vector<double>(MAKEVEC(ppl_to_dense_column_major_naive, vec)),
        std::vector<double>(MAKEVEC(ppl_to_dense_row_major_naive, vec)),
        std::vector<double>(MAKEVEC(to_dense_row_major_blocking, vec)),
        std::vector<double>(MAKEVEC(to_dense_column_major_blocking, vec)),
    };

    //system("pause");

    return 0;
}