我正在尝试新的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
因此多线程版本的速度要慢一个数量级。我使用了随机数和一个sqrt来使这个例子变得不那么简单并且易于编译器优化,所以我没有想法。
修改:
答案 0 :(得分:25)
在我的系统上,行为是相同的,但正如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;
}
答案 1 :(得分:19)
正如您所发现的那样,rand
是罪魁祸首。
对于那些好奇的人来说,这种行为可能来自你使用互斥锁实现线程安全rand
的实现。
例如,eglibc根据rand
定义__random
,is defined as:
long int
__random ()
{
int32_t retval;
__libc_lock_lock (lock);
(void) __random_r (&unsafe_state, &retval);
__libc_lock_unlock (lock);
return retval;
}
这种锁定会强制多个线程串行运行,导致性能下降。
答案 2 :(得分:8)
执行程序所需的时间非常短(33毫秒)。这意味着创建和处理多个线程的开销可能不仅仅是真正的好处。尝试使用需要较长时间执行的程序(例如,10秒)。
答案 3 :(得分:3)
为了加快速度,请使用线程池模式。
这将允许您在其他线程中排队任务,而不会在每次要使用多个线程时创建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;
}
将裸std::function
返回R
转换为无效packaged_task
,然后将其添加到任务队列。请注意,任务队列需要具有移动感知能力,因为packaged_task
是仅移动的。
注1:我对std::future
并不熟悉,所以上述内容可能有误。
注意2:如果放入上述队列的任务相互依赖于中间结果,则队列可能会死锁,因为没有描述“回收”被阻塞的线程并执行新代码。但是,“裸计算”非阻塞任务应该可以与上述模型一起正常工作。