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>, ¶m[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倍
と考えると結構遅め.
考えられる原因としては次のようなこと?
・ が小さい
・元々の演算量が なので並列化しても...
・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: Mac で AVX + マルチスレッドを試してみる - その1 - - kawa0810 のブログ
- その2: Mac で AVX + マルチスレッドを試してみる - その2 - - kawa0810 のブログ (今回)
どうでもいいけど今日でブログ開設1周年でした^^;