为什么包含rand()的c++ 11代码在多个线程中比在一个线程中慢?
Why is this C++11 code containing rand() slower with multiple threads than with one?
我正在尝试新的c++ 11线程,但是我的简单测试有糟糕的多核性能。作为一个简单的例子,这个程序将一些随机数的平方相加。
#include <iostream>
#include <thread>
#include <vector>
#include <cstdlib>
#include <chrono>
#include <cmath>
double add_single(int N) {
double sum=0;
for (int i = 0; i < N; ++i){
sum+= sqrt(1.0*rand()/RAND_MAX);
}
return sum/N;
}
void add_multi(int N, double& result) {
double sum=0;
for (int i = 0; i < N; ++i){
sum+= sqrt(1.0*rand()/RAND_MAX);
}
result = sum/N;
}
int main() {
srand (time(NULL));
int N = 1000000;
// single-threaded
auto t1 = std::chrono::high_resolution_clock::now();
double result1 = add_single(N);
auto t2 = std::chrono::high_resolution_clock::now();
auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
std::cout << "time single: " << time_elapsed << std::endl;
// multi-threaded
std::vector<std::thread> th;
int nr_threads = 3;
double partual_results[] = {0,0,0};
t1 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < nr_threads; ++i)
th.push_back(std::thread(add_multi, N/nr_threads, std::ref(partual_results[i]) ));
for(auto &a : th)
a.join();
double result_multicore = 0;
for(double result:partual_results)
result_multicore += result;
result_multicore /= nr_threads;
t2 = std::chrono::high_resolution_clock::now();
time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
std::cout << "time multi: " << time_elapsed << std::endl;
return 0;
}
在Linux和3核机器上使用'g++ -std=c++11 -pthread test.cpp'编译,典型的结果是
time single: 33
time multi: 565
所以多线程版本要慢一个数量级以上。我使用了随机数和平方根,使示例不那么琐碎,易于编译器优化,所以我没有想法。
编辑:
- 这个问题适用于更大的N,所以问题不是短运行时间 创建线程的时间不是问题。排除它不会显著改变结果
哇,我找到问题了。确实是rand()。我用c++ 11替换了它,现在运行时可以完美伸缩。谢谢大家!
在我的系统上的行为是相同的,但正如Maxim提到的,rand不是线程安全的。当我将rand改为rand_r时,那么多线程代码就像预期的那样快了。
void add_multi(int N, double& result) {
double sum=0;
unsigned int seed = time(NULL);
for (int i = 0; i < N; ++i){
sum+= sqrt(1.0*rand_r(&seed)/RAND_MAX);
}
result = sum/N;
}
正如你所发现的,rand
是罪魁祸首。
对于那些好奇的人来说,这种行为可能来自您使用互斥锁来实现rand
的线程安全。
例如,eglibc用__random
定义rand
,其定义为:
long int
__random ()
{
int32_t retval;
__libc_lock_lock (lock);
(void) __random_r (&unsafe_state, &retval);
__libc_lock_unlock (lock);
return retval;
}
这种锁会迫使多个线程串行运行,导致性能降低。
执行程序所需的时间非常小(33msec)。这意味着创建和处理多个线程的开销可能会超过实际收益。尝试使用需要更长的执行时间的程序(例如,10秒)。
为了更快,可以使用线程池模式。
这将允许您在其他线程中排队任务,而不会在每次想要使用多个线程时创建std::thread
的开销。
不要在性能指标中计算设置队列的开销,只计算排队和提取结果的时间。
创建一组线程和一个任务队列(一个包含std::function<void()>
的结构)来提供它们。线程在队列中等待新任务,完成它们,然后等待新任务。
任务负责将它们的"完成程度"传达给调用上下文,例如通过std::future<>
。让您将函数加入任务队列的代码可能会为您完成这种包装,即签名:
template<typename R=void>
std::future<R> enqueue( std::function<R()> f ) {
std::packaged_task<R()> task(f);
std::future<R> retval = task.get_future();
this->add_to_queue( std::move( task ) ); // if we had move semantics, could be easier
return retval;
}
将返回R
的裸std::function
变为虚packaged_task
,然后将其添加到任务队列中。注意,任务队列需要是移动感知的,因为packaged_task
是仅移动的。
注1:我不是很熟悉std::future
,所以上面可能是错误的。
注2:如果放入上述队列的任务在中间结果上相互依赖,则队列可能会死锁,因为没有提供"回收"被阻塞的线程并执行新代码的规定。然而,"裸计算"非阻塞任务应该可以很好地使用上述模型。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 当我在其中一个线程执行中(在activemq-cpp中)捕获到特定值时,我如何终止/停止所有其他线程
- 试图创建一个多线程程序来查找0-100000000之间的总素数
- 为什么一个向量上的多线程操作很慢
- 在另一个线程中调用luaL_error会引发qWarning
- 全局变量 多读取器 一个写入器多线程安全?
- C++一个线程如何正确通信其任务已完成?
- 最佳做法是从另一个线程访问 qml 中的Q_PROPERTY
- C++线程:如何在一个线程仍在运行时阻止另一个线程执行 (Win32)
- 是否可以创建一个从不同类调用函数的线程?
- 如何制作一个只能在一个线程上同时执行的函数?
- 结束另一个线程中使用的对象的生存期
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- 一个线程等待多个线程事件
- 两个线程一个使用流 Api,另一个线程创建文件失败并出现错误ERROR_SHARING_VIOLATION
- C 两个线程一个用于输入,一个用于输出
- 2个线程 - 一个带有OpenGL窗口的线程,第二个带有WXWIDGETS的窗口
- 每个线程一个类实例,C++11
- 多线程-一个作家和一个读者-我们需要使用储物柜吗
- Cuda如何从全局函数运行多个线程?一个运行多个线程的内核