使用多线程优化执行时间(简单示例)

时间:2012-02-04 12:23:23

标签: c++ multithreading optimization boost concurrency

我有以下代码:

#include <boost/date_time/posix_time/posix_time.hpp> 
#include <boost/cstdint.hpp> 
#include <iostream> 

int main() 
{ 
  boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); 

  boost::uint64_t sum = 0; 
  for (int i = 0; i < 1000000000; ++i) 
    sum += i; 

  boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); 
  std::cout << end - start << std::endl; 

  std::cout << sum << std::endl; 
}

任务是:重构以下程序以使用两个线程计算总计。由于现在许多处理器都有两个内核,因此使用线程会减少执行时间。

这是我的解决方案:

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>
#include <boost/cstdint.hpp> 
#include <iostream> 


boost::uint64_t s1 = 0;
boost::uint64_t s2 = 0;

void sum1() 
{ 
  for (int i = 0; i < 500000000; ++i) 
    s1 += i; 
} 

void sum2()
{ 
  for (int i = 500000000; i < 1000000000; ++i) 
    s2 += i; 
}


int main() 
{ 
    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();

    boost::thread t1(sum1); 
    boost::thread t2(sum2); 

    t1.join(); 
    t2.join();

    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); 
    std::cout << end - start << std::endl; 

    std::cout << s1+s2 << std::endl; 
} 

请查看并回答以下问题: 1.为什么这段代码实际上并没有优化执行时间? :)(我使用英特尔酷睿i5处理器和Win7 64位系统) 2.为什么当我使用一个变量 s 来存储总和而不是 s1 s2 时,总和会变得​​不正确?

提前致谢。

4 个答案:

答案 0 :(得分:4)

我会回答你的第二个问题,因为第一个问题还不清楚。当您使用单个全局变量来计算总和时,存在由操作事实引起的所谓“数据竞争”

s += i;

不是“原子”,意味着在汇编程序级别它被翻译成几个指令。如果一个线程正在执行这组指令,它可能被另一个执行相同操作的线程中断,并且结果将不一致。

这是因为操作系统在线程上调度了线程,并且无法预测线程如何交错执行指令。

这种情况下的经典模式是有两个局部变量收集每个线程的总和,然后在线程完成其工作后将它们一起汇总成一个全局变量。

答案 1 :(得分:1)

我会回答第一个问题:

创建一个线程需要花费更多的时间而不是什么都不做(基础)。

编译器会将其转换为:

for (int i = 0; i < 1000000000; ++i) 
     sum += i; 

进入这个:

//    << optimized away >>

即使是使用本地数据的最坏情况,也可以在启用优化的情况下添加一个。

并行版本降低了编译器优化程序的能力,同时增加了工作量。

答案 2 :(得分:1)

重构程序(代码方式)以使用多个线程计算总和的最简单方法是使用OpenMP

// $ g++ -fopenmp parallel-sum.cpp && ./a.out
#include <stdint.h>
#include <iostream>

const int32_t N = 1 << 30;

int main() {
  int64_t sum = 0;
#pragma omp parallel for reduction(+:sum)
  for (int32_t i = 0; i < N; ++i)
    sum += i;

  std::cout << sum << " " << static_cast<int64_t>(N)*(N-1)/2 << std::endl;
}

输出

576460751766552576 576460751766552576

这是使用c ++ 11线程实现的并行缩减:

// $ g++ -std=c++0x -pthread parallel-sum-c++11.cpp && ./a.out
#include <cstdint>
#include <iostream>
#include <thread>

namespace {
  std::mutex mutex;

  void sum_interval(int32_t start, int32_t end, int64_t &sum) {
    int64_t s = 0;
    for ( ; start < end; ++start) s += start;

    std::lock_guard<std::mutex> lock(mutex);
    sum += s; 
  }
}

int main() {
  int64_t sum = 0;
  const int num_threads = 4;
  const int32_t N = 1 << 30;
  std::thread t[num_threads];

  // fork threads; assign intervals to sum
  int32_t start = 0, step = N / num_threads;
  for (int i = 0; i < num_threads-1; ++i, start += step)
    t[i] = std::thread(sum_interval, start, start+step, std::ref(sum));
  t[num_threads-1] = std::thread(sum_interval, start, N, std::ref(sum));

  // wait for result and print it
  for (int i = 0; i < num_threads; ++i) t[i].join();
  std::cout << sum << " " << static_cast<int64_t>(N)*(N-1)/2 << std::endl;
}

注意:对sum的访问受到保护,因此一次只能有一个线程可以更改它。如果sumstd::atomic<int64_t>,则可以省略锁定。

答案 3 :(得分:1)

1的答案应该是:在分析器中运行并查看它告诉你的内容。

但至少有一个常见的嫌疑人:虚假分享。您的s1和s2可能最终位于同一个高速缓存行,因此您的2个内核(如果您的2个线程确实最终位于不同的内核上)必须在高速缓存行级别进行同步。确保2 uint64_t位于不同的缓存行(其大小取决于您要定位的体系结构)。

至于2的答案...程序中没有任何内容可以保证来自一个线程的更新不会被第二个线程踩到,反之亦然。您需要同步原语以确保您的更新不会同时发生,或者需要原子更新以确保更新不会相互踩踏。