异步的令人困惑的行为
Puzzling behaviour of async
这可能是一些奇怪的Linux怪癖,但我观察到了非常奇怪的行为。
下面的代码应该将数字求和的同步版本与异步版本进行比较。问题是,我看到了性能的提高(它不是缓存,即使我将代码拆分为两个单独的程序也会发生这种情况),同时仍然将程序视为单线程(只使用了一个核心)。
strace
确实显示了一些线程活动,但像top
克隆这样的监视工具仍然只显示一个已使用的核心。
我观察到的第二个问题是,如果我增加派生比率,内存使用量就会爆炸式增长。线程的内存开销是多少?使用5000个线程,我可以使用大约10GB的内存。
#include <iostream>
#include <random>
#include <chrono>
#include <future>
using namespace std;
long long sum2(const vector<int>& v, size_t from, size_t to)
{
const size_t boundary = 5*1000*1000;
if (to-from <= boundary)
{
long long rsum = 0;
for (;from < to; from++)
{
rsum += v[from];
}
return rsum;
}
else
{
size_t mid = from + (to-from)/2;
auto s2 = async(launch::async,sum2,cref(v),mid,to);
long long rsum = sum2(v,from,mid);
rsum += s2.get();
return rsum;
}
}
long long sum2(const vector<int>& v)
{
return sum2(v,0,v.size());
}
long long sum(const vector<int>& v)
{
long long rsum = 0;
for (auto i : v)
{
rsum += i;
}
return rsum;
}
int main()
{
const size_t vsize = 100*1000*1000;
vector<int> x;
x.reserve(vsize);
mt19937 rng;
rng.seed(chrono::system_clock::to_time_t(chrono::system_clock::now()));
uniform_int_distribution<uint32_t> dist(0,10);
for (auto i = 0; i < vsize; i++)
{
x.push_back(dist(rng));
}
auto start = chrono::high_resolution_clock::now();
long long suma = sum(x);
auto end = chrono::high_resolution_clock::now();
cout << "Sum is " << suma << endl;
cout << "Duration " << chrono::duration_cast<chrono::nanoseconds>(end - start).count() << " nanoseconds." << endl;
start = chrono::high_resolution_clock::now();
suma = sum2(x);
end = chrono::high_resolution_clock::now();
cout << "Async sum is " << suma << endl;
cout << "Async duration " << chrono::duration_cast<chrono::nanoseconds>(end - start).count() << " nanoseconds." << endl;
return 0;
}
也许您观察到使用了一个核心,因为同时工作的线程之间的重叠太短而不明显。在现代硬件上,从连续的内存区域求和5mln值应该非常快,所以当父母完成求和时,孩子可能刚刚开始,父母可能会花费大部分或全部时间等待孩子的结果。你有没有试着增加工作单位,看看重叠是否变得明显?
关于提高性能:即使由于工作单元太小,线程之间有0个重叠,多线程版本仍然可以从额外的一级缓存中受益。对于这样的测试,内存可能是一个瓶颈,顺序版本将只使用一个一级缓存,而多线程版本将使用尽可能多的内核。
您检查了正在打印的时间吗?在我的机器上,串行时间在-O2下不到1秒,而并行求和时间快几倍。因此,完全有可能CPU的使用时间不够长,无法注册"top"之类的东西,因为它们通常每秒只刷新一次。
如果通过减少每个线程的计数来增加线程数量,那么就有效地增加了线程管理的开销。如果有5000个线程处于活动状态,那么您的任务将占用额外内存中5000*分钟的线程堆栈大小。在我的机器上,它是20Gb!
为什么不尝试增加源容器的大小?如果您使并行部分花费足够长的时间,您将看到相应的并行CPU使用情况。然而,请做好准备:整数求和速度很快,生成随机数所需的时间可能比将这些数字相加所用的时间长一两个数量级。
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何在C++中实现带有packaged_task的异步等待循环?
- 创建 Spdlog 异步文件记录器时遇到困难
- C ++异步键盘输入(标准方式)
- 在 gtkmm 中异步加载图像
- 带有 Boost.Beast 的异步读取标头
- 如何在 c++ 中异步调用静态方法?
- libcurl :C++处理多个异步请求
- 如何在C++上启动异步线程
- TCP 服务器的异步读取使用 boost::asio 打印客户端套接字发送的数据
- 增强 ASIO 和串行端口异步读取
- 使用 Qt5 SQL 进行异步数据库访问的策略
- 如何使用从处理程序调度的最终回调将响应异步返回给调用方on_read?
- C++中真正的异步文件 IO
- 提升::Asio 异步聊天客户端停止与服务器通信
- 在 c++ 托管和异步运行中调用 c# 可执行文件
- std::异步与非静态成员函数
- 我有一个关于C++提升的问题:: asio 和 std :: 异步
- 某些 boost::asio 异步函数是否将处理程序连接到操作,以便处理程序被触发一次?
- std::异步导致死锁?