这可能是一些奇怪的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;
}
答案 0 :(得分:1)
也许您观察到一个核心被使用,因为同时工作的线程之间的重叠太短而不明显。在现代硬件上对连续存储区域中的5mln值进行求和应该非常快,因此在父项完成求和时,孩子可能几乎没有开始,父母可能花费大部分或全部时间等待孩子的结果。您是否尝试增加工作单位以查看重叠是否变得明显?
关于提高性能:即使由于工作单元太小而线程之间存在0重叠,多线程版本仍然可以从额外的L1高速缓存中受益。对于这样的测试,内存可能是一个瓶颈,顺序版本将只使用一个L1缓存,而多线程版本将使用尽可能多的内核。
答案 1 :(得分:1)
您是否检查过正在打印的时间?在我的机器上,串联时间在-O2下不到1秒,而并行和时间快几倍。因此,完全有可能CPU使用时间不够长,无法注册“top”之类的东西,因为它们通常每秒只刷一次。
如果通过减少每线程计数来增加线程数,则会有效地增加线程管理的开销。如果你有5000个线程处于活动状态,那么你的任务将在额外的内存中占用5000 * min-thread-stack-size。在我的机器上是20Gb!
为什么不尝试增加源容器的大小?如果使并行部分花费足够长的时间,您将看到相应的并行CPU使用情况。但是,要做好准备:求和整数快,生成随机数所花费的时间可能比将数字加在一起的时间长一个数量级或两倍。