メモリアライメントを揃えずに SIMD する方法

メモリアライメントを揃えずに SIMD 演算する方法

今までは _mm_malloc() を使用してメモリアライメントを 32byte 境界に揃えてから AVX 命令で SIMD 演算をしていましたが,メモリアライメントを気にせずに AVX を用いて SIMD 演算する方法です.

データを 32byte 境界に揃えている場合は _mm_load_ps や _mm_load_pd が暗黙的に使われデータをロードします.計算したいデータが 32byte 境界に揃えていない場合は _mm_loadu_ps や _mm_storeu_ps 等を明示的に使用することで SIMD 演算を使用することが可能になります.ただし,オーバーヘッドが発生するため計算が遅くなるそうで可能な限りデータは 32byte 境界に揃えましょう.

総和計算のサンプルコード

メモリアライメントを気にせずに総和計算をするサンプルコードを以下に記載します.

/*--------------------
  sum_loadu_storeu.cpp
  --------------------*/

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <numeric>
#include <immintrin.h>

float vec_sum(std::size_t const n, float const* x){
  std::size_t static const para = 8;
  float static sum[para];

  std::size_t const end = (n / para) * para;

  __m256 vsum = _mm256_setzero_ps();
  for(std::size_t i=0; i<end; i+=para)
    vsum = _mm256_add_ps( vsum, _mm256_loadu_ps(x+i) );
  _mm256_storeu_ps(sum, vsum);
  for(std::size_t i=end; i<n; ++i)
    sum[0] += x[i];
  
  sum[0] += sum[1] + sum[2] + sum[3] + sum[4] + sum[5] + sum[6] + sum[7];
  
  return sum[0];
}

double vec_sum(std::size_t const n, double const* x){
  std::size_t static const para = 4;
  double static sum[para];

  std::size_t const end = (n / para) * para;

  __m256d vsum = _mm256_setzero_pd();
  for(std::size_t i=0; i<end; i+=para)
    vsum = _mm256_add_pd( vsum, _mm256_loadu_pd(x+i) );
  _mm256_storeu_pd(sum, vsum);
  for(std::size_t i=end; i<n; ++i)
    sum[0] += x[i];
  
  sum[0] += sum[1] + sum[2] + sum[3];
  
  return sum[0];
}

int main(void){
  std::size_t const n = 102;
  float* x = static_cast<float*>( malloc(sizeof(float) * n) );
  double* y = static_cast<double*>( malloc(sizeof(double) * n) );

  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\n";
  std::cout << "-Accumulate: " << std::accumulate(x, x+n, 0.0) << std::endl;  
  std::cout << "-SIMD: " << vec_sum(n, x) << std::endl;

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

  free(x);
  free(y);

  return 0;
}

アライメントの管理

C++0x からメモリアライメントの管理がサポートされています.

  • alignas: 変数宣言時にアライメントを指定する
  • alignof: 変数のアライメントを取得する

ただし,現在の gcc4.7 ではサポートされていないようです.http://gcc.gnu.org/gcc-4.7/cxx0x_status.html

alignof 等をうまく使えれば切り替えが簡単にできるようになる気がします.ついでに,load を使ってプログラムを書くとキャッシュ制御等もしやすくなるので今後 (ry ...