マルチスレッドプログラミングで総和計算 - その2 std::thread & std::async 編-

2012-08-04 - kawa0810の日記 の続きです.
今回は C++11 で追加された機能の一つである std::thread を用いて総和計算の高速化を考えます.

std::thread と std::async について

std::thread は C++11 から追加されたスレッド操作のライブラリです.std::thread を使用してスレッドを実行することで以下のメリットが得られます.

  • スレッドで実行させる関数の返り値と引数を void* にしなくてよい
  • 引数は何個でも渡せる + オブジェクトを引数にすることも可能
  • 例外処理で扱うことができる

std::thread はプリミティブなオブジェクトであり以下の問題があります.

  • スレッド内で例外を投げてはいけない
  • 関数の返り値を取得できない

よって,std::thread の代わりに std::packaged_task や std::async の使用が推奨されています.

std::async を用いた総和計算

今回は std::async を用いた総和計算の例を以下に記載します.

/**--------------------
   async_sum.cpp
   --------------------*/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <numeric>
#include <thread>
#include <future>

std::size_t const CACHE_LINE = 32;
std::size_t const NUM_THREAD = 4;

template<class T>
T vec_sum_thread(std::size_t const tid, std::size_t const n, T const* x){
  std::size_t len = (n + NUM_THREAD - 1) / NUM_THREAD;
  std::size_t const beg = tid * len;
  std::size_t const end = (tid == NUM_THREAD - 1 ? n : beg + len);
 
  T sum = 0.0;
  for(std::size_t i=beg; i<end; ++i){
    sum += x[i];
  }

  return sum;
}

template<class T>
T vec_sum(std::size_t const n, T const* x){
  std::future<T> thread[NUM_THREAD];

  for(std::size_t i=0; i<NUM_THREAD; ++i){
    thread[i] = std::async(std::launch::async, vec_sum_thread<T>, i, n, x);
  }

  T sum = 0.0;
  for(std::size_t i=0; i<NUM_THREAD; ++i){
    sum += thread[i].get();
  }

  return sum;
}

int main(void){
  std::size_t const n = 1023;
  float* x;
  double* y;

  posix_memalign(reinterpret_cast<void**>(&x), CACHE_LINE, n * sizeof(float));
  posix_memalign(reinterpret_cast<void**>(&y), CACHE_LINE, n * sizeof(double));

  srand( static_cast<unsigned>(time(NULL)) );
  for(std::size_t i=0; i<n; ++i) 
    x[i] = static_cast<float>( rand() ) / RAND_MAX;

  for(std::size_t i=0; i<n; ++i)
    y[i] = static_cast<double>( rand() ) / RAND_MAX;

  std::cout << "Single Precision" << std::endl;
  std::cout << "- Accumulate: " << std::accumulate(x, x+n, 0.0) << std::endl;
  std::cout << "- vec_add: " << vec_sum(n, x) << std::endl;

  std::cout << "\nDouble Precision" << std::endl;
  std::cout << "- Accumulate: " << std::accumulate(y, y+n, 0.0) << std::endl;
  std::cout << "- vec_add: " << vec_sum(n, y) << std::endl;

  free(x);
  free(y);

  return 0;
}

std::async の使い方

・インクルードするファイル

#include <thread>
#include <future>

また,std::thread と std::future, std::async は C++11 から追加されたので gccコンパイルする際は -std=c++11 が必要となります

$ g++-mp-4.7 async_sum.cpp -std=c++11

・std::future
std::future は計算処理の同期と結果の取得を提供してくれます.今回は std::async で作成するのでスレッドの生成部分は以下のようになります

std::async(std::launch::async, func, arg1, arg2,...);
//std::launch::async: std::async でスレッドを生成
//func: スレッドで実行させる関数
//arg1, arg2,...: 関数の引数.何個でも渡すことが可能

また,関数の返り値を取得したい場合は thread.get() を使用することで返り値を取得することができます.

マルチスレッドプログラミングで総和計算