为什么并行版本的累加会慢得多
Why would a parallel version of accumulate be so much slower?
受Antony Williams"C++并发在行动"的启发,我仔细研究了他的并行版本std::accumulate
。我从书中复制了它的代码,并添加了一些用于调试的输出,这就是我最终得到的:
#include <algorithm>
#include <future>
#include <iostream>
#include <thread>
template <typename Iterator, typename T>
struct accumulate_block
{
T operator()(Iterator first, Iterator last)
{
return std::accumulate(first, last, T());
}
};
template <typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init)
{
const unsigned long length = std::distance(first, last);
if (!length) return init;
const unsigned long min_per_thread = 25;
const unsigned long max_threads = (length) / min_per_thread;
const unsigned long hardware_conc = std::thread::hardware_concurrency();
const unsigned long num_threads = std::min(hardware_conc != 0 ? hardware_conc : 2, max_threads);
const unsigned long block_size = length / num_threads;
std::vector<std::future<T>> futures(num_threads - 1);
std::vector<std::thread> threads(num_threads - 1);
Iterator block_start = first;
for (unsigned long i = 0; i < (num_threads - 1); ++i)
{
Iterator block_end = block_start;
std::advance(block_end, block_size);
std::packaged_task<T(Iterator, Iterator)> task{accumulate_block<Iterator, T>()};
futures[i] = task.get_future();
threads[i] = std::thread(std::move(task), block_start, block_end);
block_start = block_end;
}
T last_result = accumulate_block<Iterator, T>()(block_start, last);
for (auto& t : threads) t.join();
T result = init;
for (unsigned long i = 0; i < (num_threads - 1); ++i) {
result += futures[i].get();
}
result += last_result;
return result;
}
template <typename TimeT = std::chrono::microseconds>
struct measure
{
template <typename F, typename... Args>
static typename TimeT::rep execution(F func, Args&&... args)
{
using namespace std::chrono;
auto start = system_clock::now();
func(std::forward<Args>(args)...);
auto duration = duration_cast<TimeT>(system_clock::now() - start);
return duration.count();
}
};
template <typename T>
T parallel(const std::vector<T>& v)
{
return parallel_accumulate(v.begin(), v.end(), 0);
}
template <typename T>
T stdaccumulate(const std::vector<T>& v)
{
return std::accumulate(v.begin(), v.end(), 0);
}
int main()
{
constexpr unsigned int COUNT = 200000000;
std::vector<int> v(COUNT);
// optional randomising vector contents - std::accumulate also gives 0us
// but custom parallel accumulate gives longer times with randomised input
std::mt19937 mersenne_engine;
std::uniform_int_distribution<int> dist(1, 100);
auto gen = std::bind(dist, mersenne_engine);
std::generate(v.begin(), v.end(), gen);
std::fill(v.begin(), v.end(), 1);
auto v2 = v; // copy to work on the same data
std::cout << "starting ... " << 'n';
std::cout << "std::accumulate : t" << measure<>::execution(stdaccumulate<int>, v) << "us" << 'n';
std::cout << "parallel: t" << measure<>::execution(parallel<int>, v2) << "us" << 'n';
}
这里最有趣的是,几乎总是从std::accumulate
得到0长度的时间。
示例输出:
starting ...
std::accumulate : 0us
parallel:
inside1 54us
inside2 81830us
inside3 89082us
89770us
这里有什么问题?
http://cpp.sh/6jbt
与微基准测试的常见情况一样,您需要确保您的代码实际上在做一些事情。你正在做一个accumulate
,但实际上你并没有把结果存储在任何地方,也没有用它做任何事情。那么,你真的需要做任何工作吗?编译器只是在正常情况下删掉了所有的逻辑。这就是为什么你得到0
。
只需更改您的代码即可确保工作需要完成。例如:
int s, s2;
std::cout << "starting ... " << 'n';
std::cout << "std::accumulate : t"
<< measure<>::execution([&]{s = std::accumulate(v.begin(), v.end(), 0);})
<< "usn";
std::cout << "parallel: t"
<< measure<>::execution([&]{s2 = parallel_accumulate(v2.begin(), v2.end(), 0);})
<< "usn";
std::cout << s << ',' << s2 << std::endl;
相关文章:
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 导入库可以跨dll版本工作吗
- 在调用FreeLibrary后,释放动态链接到具有相同版本的CRT堆的DLL的内存
- C++17中的并行执行策略
- 在clang++预处理器中确定gcc工具链版本
- 并行用于C++17中数组索引范围内的循环
- 码头化的C++应用程序是否向后兼容早期的内核版本
- 如何在Elixir中调用递归函数并行
- OpenMP:并行更新数组总是需要减少数组吗
- 不同的Visual Studio版本中缺少.dll
- 用符号版本替换对函数的所有调用
- luaL_dofile在已知良好的字节码上失败,可以使用未编译的版本
- 字符串搜索 - 并行版本较慢
- 为什么并行版本的累加会慢得多
- 如何以最少的重复管理并行和顺序版本代码
- 顺序和并行版本给出不同的结果 - 为什么
- 如何同时使用并行和串行版本的MKL
- 将Xcode 3和Xcode 4工具链转化为可并行使用的版本包
- 在执行并行版本时,双重免费或损坏(fasttop)
- OpenMP 并行版本的运行速度比串行版本慢