Mac で AVX + マルチスレッドを試してみる - その2 -

主旨

Mac で AVX + マルチスレッドを試してみる - その1 - - kawa0810 のブログ の続きです.
Pthread + AVX on OSX 環境を試した結果です.問題は前回に続き総和計算(マルチスレッドプログラミングで総和計算 - その1 Pthread 編- - kawa0810 のブログ)を扱います.

ソースコード

/**--------------------
   pthread_avx_sum.cpp
   --------------------*/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <numeric>
#include <sys/time.h>

#include <pthread.h>//-lpthread
#include <immintrin.h>//-mavx

template<class T>
struct Pthread_sum{
  std::size_t tid;
  std::size_t n;
  T* x;
  T sum;
};

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

double second(void){
  struct timeval t;
  gettimeofday(&t, NULL);
  return t.tv_sec + t.tv_usec * 1.0e-6;
}

float vec_sum_avx(std::size_t const beg, std::size_t const end, float* x){
  std::size_t const para = CACHE_LINE / sizeof(float);
  std::size_t const end_simd = (end / para) * para;

  auto vsum = _mm256_setzero_ps();
  float* sum = reinterpret_cast<float*>(&vsum);

  for(std::size_t i=beg; i<end_simd; i+=para){
    vsum = _mm256_add_ps(vsum, _mm256_loadu_ps(x+i) );
  }
  for(std::size_t i=end_simd; i<end; ++i){
    sum[0] += x[i];
  }

  for(std::size_t i=1; i<para; ++i){
    sum[0] += sum[i];
  }

  return sum[0];
}


double vec_sum_avx(std::size_t const beg, std::size_t const end, double* x){
  std::size_t const para = CACHE_LINE / sizeof(double);
  std::size_t const end_simd = (end / para) * para;

  auto vsum = _mm256_setzero_pd();
  double* sum = reinterpret_cast<double*>(&vsum);

  for(std::size_t i=beg; i<end_simd; i+=para){
    vsum = _mm256_add_pd(vsum, _mm256_loadu_pd(x+i) );
  }
  for(std::size_t i=end_simd; i<end; ++i){
    sum[0] += x[i];
  }

  for(std::size_t i=1; i<para; ++i){
    sum[0] += sum[i];
  }

  return sum[0];
}


template<class T>
void* vec_sum_thread(void* args){
  Pthread_sum<T>* param = static_cast<Pthread_sum<T>*>(args);
  std::size_t len = (param->n + NUM_THREAD - 1) / NUM_THREAD;
  std::size_t const beg = param->tid * len;
  std::size_t const end = (param->tid == NUM_THREAD - 1 ? param->n : beg+len);

  param->sum = vec_sum_avx(beg, end, param->x);
}


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

  for(std::size_t i=0; i<NUM_THREAD; ++i){
    param[i].tid = i;
    param[i].n = n;
    param[i].x = x;
    pthread_create(&thread[i], NULL, vec_sum_thread<T>, &param[i]);
  }

  for(std::size_t i=0; i<NUM_THREAD; ++i)
    pthread_join(thread[i], NULL);

  for(std::size_t i=1; i<NUM_THREAD; ++i){
    param[0].sum += param[i].sum;
  }

  return param[0].sum;
}


int main(void){
  std::size_t const n = 100000;
  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;
  {
    double start = second();
    float sum = std::accumulate(x, x+n, 0.0);
    double stop = second();
    std::cout << "- Accumulate: " << sum 
	      << ", " << (stop - start) << " [sec]" << std::endl; 
  }
  {
    double start = second();
    float sum = vec_sum(n, x);
    double stop = second();
    std::cout << "- vec_sum: " << sum
	      << ", " << (stop - start) << " [sec]" << std::endl;     
  }

  std::cout << "Double Precision" << std::endl;
  {
    double start = second();
    double sum = std::accumulate(y, y+n, 0.0);
    double stop = second();
    std::cout << "- Accumulate: " << sum 
	      << ", " << (stop - start) << " [sec]" << std::endl; 
  }
  {
    double start = second();
    double sum = vec_sum(n, y);
    double stop = second();
    std::cout << "- vec_sum: " << sum
	      << ", " << (stop - start) << " [sec]" << std::endl;     
  }

  free(x);
  free(y);

  return 0;
}

コンパイル方法

g++-mp-4.7 -S pthread_avx_sum.cpp -std=c++11 -mavx -lpthread 
clang++-mp-3.3 pthread_avx_sum.s -std=c++11 -mavx -lpthread

考察(?)

手元での実行結果は以下の通り.

Single Precision
- Accumulate: 49947.1, 0.000351191 [sec]
- vec_sum: 49947.1, 0.000123978 [sec]
Double Precision
- Accumulate: 50100.3, 0.000365019 [sec]
- vec_sum: 50100.3, 0.000169039 [sec]

std::accumulate に比べて,
・単精度 (float) :2.83倍
・倍精度 (double) :2.15倍
程度の高速化.
今回は2コア + AVX なので理論的には
・単精度:2コア x AVX (8並列) = 16倍
・倍精度:2コア x AVX (4並列) = 8倍
と考えると結構遅め.

考えられる原因としては次のようなこと?
 n が小さい
・元々の演算量が  O(n) なので並列化しても...
・Pthread で使用する構造体の準備やらのオーバヘッドの割合が大きい
・_mm256_loadu_p?() が遅いので,_mm256_load_p?() で安定して計算できる並列化方法にする

改善点

  • コードが汚いのできれいにする orz
  • gettimeofday が deprecated なので clock_gettime にしたいけど,動かなかった *1
  • AVX API 部分をオーバーロードで呼び出しできるようにする
inline
__m256 ksimd_add(__m256 x, __m256 y){
  return _mm256_add_ps(x, y);
}

inline
__m256d ksimd_add(__m256d x, __m256d y){
  return _mm256_add_pd(x, y);
}

上記の方法だと,__m256 型が値渡しで処理されてしまうので実行時間が遅くなった... _mm256_setzero_p?() の呼び出し部分は std::enable_if を使えばうまく呼び出しができそう.

#include <type_traits>
extern void* enabler;

template<class T, 
        typename std::enable_if<std::is_same<T, float>::value>::type*& = enabler > 
inline
  __m256 ksimd_setzero(void){
  return _mm256_setzero_ps();
}
  
template<class T, 
        typename std::enable_if<std::is_same<T, double>::value>::type*& = enabler > 
inline
  __m256d ksimd_setzero(void){
  return _mm256_setzero_pd();
}
Mac で AVX + マルチスレッドを試してみる

どうでもいいけど今日でブログ開設1周年でした^^;