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;
}