这可能是一些奇怪的Linux怪癖,但我观察到非常奇怪的行为。
下面的代码应该将汇总数字的同步版本与asynchronous版本进行比较。 事情是,我看到性能提高(这不是caching,即使将代码分成两个独立的程序也是如此),同时仍然将程序视为单线程(仅使用一个内核)。
strace
确实显示了一些线程活动,但像top
克隆这样的监视工具仍然只显示一个已使用的内核。
我观察到的第二个问题是,如果我增加产卵率,内存使用情况只是爆炸。 线程的内存开销是多less? 有了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; }
也许你观察到一个被使用的核心,因为同时工作的线程之间的重叠太短而不明显。 在现代硬件上,从一个连续的内存区域中总结出5毫升的值应该是非常快的,所以到了父节点求和的时候,孩子可能几乎没有开始,父母可能花费大部分或所有的时间来等待孩子的结果。 你有没有试图增加工作单位,看看重叠是否明显?
关于性能的提高:即使线程之间由于工作单元太小而存在0重叠,多线程版本仍然可以从额外的L1高速缓存中受益。 对于这样的测试,内存可能会成为瓶颈,顺序版本将只使用一个L1缓存,而多线程版本将使用尽可能多的内核。
你有没有检查正在打印的时间? 在我的机器上,串行时间在-O2下小于1s,而并行总和时间要快几倍。 因此,完全有可能CPU的使用时间不够长,比如“top”注册,因为它们通常每秒只刷新一次。
如果通过减少每个线程的数量来增加线程的数量,则可以有效地增加线程管理的开销。 如果你有5000个线程活动,那么你的任务将在额外的内存中占用5000 * min-thread-stack-size。 在我的机器上是20Gb!
为什么不尝试增加源容器的大小? 如果使并行部分花费足够长时间,则会看到相应的并行CPU使用情况。 但是,要做好准备:将整数加起来是快的 ,产生随机数的时间可以比将数字加在一起的时间要长一个数量级或两个数量级。