質問内容
私は現在simd拡張命令(AVX 256bit)を使用して
大きなベクトル同士の内積を高速化しようとしており、下記コードを作成し
通常の内積関数とsimd版内積関数とで検算を行いました。
しかし結果は下記実行結果のようになり
結果が合いませんでした。
なぜ計算結果が合わないのでしょうか。
色々考えたり調べてみましたがそれらしい情報に
有りつけなかったのでご質問させていただきます。
どうか御回答よろしくお願いいたします.
問題の発生するコード
c++
1#include <iostream> 2#include <cstdint> 3#include <immintrin.h> 4#include <iomanip> 5 6inline size_t round_up(const size_t close_to_num, const size_t divisible_by)noexcept{// divisible_byで割り切れる最もclose_to_numに近い整数を算出 7 return divisible_by == 0 ? close_to_num : close_to_num + divisible_by - (close_to_num % divisible_by); 8} 9 10float dot_avx(const int n, const float* const a, const float* const b)noexcept{// simdでの内積計算 11 __m256 ymm_reg = _mm256_setzero_ps(); 12 13 for(size_t i = 0; i < n; i += 8) ymm_reg = _mm256_add_ps(ymm_reg, _mm256_mul_ps(_mm256_load_ps(a + i), _mm256_load_ps(b + i))); 14 15 alignas(32) float array[8]; 16 _mm256_store_ps(array, ymm_reg); 17 18 return array[0] + array[1] + array[2] + array[3] + array[4] + array[5] + array[6] + array[7]; 19} 20 21float dot(const int n, const float* const a, const float* const b)noexcept{// simdを使わず内積計算 22 float sum = 0; 23 for(int i = 0; i < n; ++i) sum += a[i] * b[i]; 24 return sum; 25} 26 27int main(){ 28 29 size_t apparent_size = 1234;// 見かけ上のサイズ 30 size_t true_size;// 本当の配列サイズ 31 32 apparent_size % 8 ? true_size = round_up(apparent_size, 8) : true_size = apparent_size; 33 34 auto* mem1 = static_cast<float*>(aligned_alloc(32, sizeof(float)*true_size)); 35 auto* mem2 = static_cast<float*>(aligned_alloc(32, sizeof(float)*true_size)); 36 37 for(unsigned i = 0; i<true_size; ++i){ 38 mem1[i] = 0.f; 39 mem2[i] = 0.f; 40 } 41 42 for(unsigned i = 0; i<apparent_size; ++i){ 43 mem1[i] = i; 44 mem2[i] = i; 45 } 46 47 auto result_avx = dot_avx(true_size, mem1, mem2); 48 auto result_nomal = dot(true_size, mem1, mem2); 49 50 std::cout << "simd利用時の結果:" << std::fixed << std::setprecision(20) << result_avx << std::endl; 51 std::cout << "simdを利用せず計算した場合の結果:" << std::fixed << std::setprecision(20) << result_nomal << std::endl; 52 std::cout << "結果の差 = " << std::fixed << std::setprecision(20) << result_avx - result_nomal << std::endl; 53 result_avx == result_nomal ? std::cout << "計算結果が一致" << std::endl : std::cout << "計算結果が違う" << std::endl << std::endl; 54 55 std::free(mem1); 56 std::free(mem2); 57 58 return 0; 59}
上記コードの実行結果
terminal
1simd利用時の結果:625598848.00000000000000000000 2simdを利用せず計算した場合の結果:625598656.00000000000000000000 3結果の差 = 192.00000000000000000000 4計算結果が違う 5
開発環境の備考
種類 | 名前 | バージョン | 備考 |
---|---|---|---|
os | Linux Mint | 20 | - |
コンパイラ | glang++ | 10 | 本文中のコードをコンパイルする時オプションに-mavxと-std=c++2aを指定 |
cpu | Intel© Core™ i7-7700 CPU @ 3.60GHz × 4 | - | - |
ビルドツール | cmake | 3.16.3 | - |
追記(2020/10/29)
raccyさんが回答内で教えて下さった__カハンの総和アルゴリズム__を用いて
内積関数を作成してみたところ誤差が減少したので追記しておきます。
c++
1#include <iostream> 2#include <cstdint> 3#include <immintrin.h> 4#include <iomanip> 5 6inline size_t round_up(const size_t close_to_num, const size_t divisible_by)noexcept{// divisible_byで割り切れる最もclose_to_numに近い整数を算出 7 return divisible_by == 0 ? close_to_num : close_to_num + divisible_by - (close_to_num % divisible_by); 8} 9 10float dot_avx(const size_t n, const float* const a, const float* const b)noexcept{// simdでの内積計算(カハンの加算アルゴリズム使用) 11 // ここからa[i]*b[i]の内容を8つに分けてymmレジスタ(変数sum_avx)格納 12 __m256 sum_avx, loss_avx, y_avx, t_avx; 13 sum_avx = _mm256_mul_ps(_mm256_load_ps(a), _mm256_load_ps(b)); 14 loss_avx = _mm256_setzero_ps(); 15 for(size_t i = 8; i < n; i += 8){ 16 y_avx = _mm256_sub_ps(_mm256_mul_ps(_mm256_load_ps(a + i), _mm256_load_ps(b + i)), loss_avx); 17 t_avx = _mm256_add_ps(sum_avx, y_avx); 18 loss_avx = _mm256_sub_ps(_mm256_sub_ps(t_avx, sum_avx), y_avx); 19 sum_avx = t_avx; 20 } 21 22 alignas(32) float array[8]; 23 _mm256_store_ps(array, sum_avx); 24 25 // ここから通常メモリ(変数array)の内容総和 26 float sum, loss, y, t; 27 sum = array[0]; 28 loss = 0.f; 29 for(size_t i = 1; i < 8; ++i){ 30 y = array[i] - loss; 31 t = sum + y; 32 loss = (t - sum) - y; 33 sum = t; 34 } 35 return sum; 36} 37 38float dot(const size_t n, const float* const a, const float* const b)noexcept{// simdを使わず内積計算(カハンの加算アルゴリズム使用) 39 float sum, loss, y, t; 40 sum = a[0] * b[0]; 41 loss = 0.f; // 情報落ちによって失われてしまう数値を保管するための変数 42 for(size_t i = 1; i < n; ++i){ 43 y = a[i] * b[i] - loss; // a[i]*b(i)に前回の変数tの計算で失われてしまった部分を反映 44 t = sum + y; // sumにyを足している(sumが大きな値でyが小さな値ならば情報落ちにより大きい誤差が発生) 45 loss = (t - sum) - y; // tを計算する時に失われた部分 46 sum = t; 47 } 48 return sum; 49} 50 51int main(){ 52 size_t apparent_size = 1234;// 見かけ上のサイズ 53 size_t true_size = (apparent_size % 8 ? round_up(apparent_size, 8) : apparent_size);// 本当の配列サイズ 54 55 auto* mem1 = static_cast<float*>(aligned_alloc(32, sizeof(float)*true_size)); 56 auto* mem2 = static_cast<float*>(aligned_alloc(32, sizeof(float)*true_size)); 57 58 for(unsigned i = 0; i<true_size; ++i){ 59 mem1[i] = 0.f; 60 mem2[i] = 0.f; 61 } 62 63 for(unsigned i = 0; i<apparent_size; ++i){ 64 mem1[i] = i; 65 mem2[i] = i; 66 } 67 68 auto result_avx = dot_avx(true_size, mem1, mem2); 69 auto result_nomal = dot(true_size, mem1, mem2); 70 71 std::cout << std::fixed << std::setprecision(20) << std::endl; 72 std::cout << "simd利用時の結果:" << result_avx << std::endl; 73 std::cout << "simdを利用せず計算した場合の結果:" << result_nomal << std::endl; 74 std::cout << "結果の差 = " << result_avx - result_nomal << std::endl; 75 std::cout << (result_avx == result_nomal ? "計算結果が一致" : "計算結果が違う") << std::endl; 76 77 std::free(mem1); 78 std::free(mem2); 79 80 return 0; 81}
結果
terminal
1simd利用時の結果:625599104.00000000000000000000 2simdを利用せず計算した場合の結果:625599104.00000000000000000000 3結果の差 = 0.00000000000000000000 4計算結果が一致
正確な内積結果:625599129
本コードの内積結果:625599104
差:25
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/10/21 13:12
2020/10/22 12:59 編集
退会済みユーザー
2020/10/22 11:48 編集
2020/10/22 12:56
退会済みユーザー
2020/10/23 09:45 編集
2020/10/23 10:02
退会済みユーザー
2020/10/23 10:08