g ++ sse内因性ジレンマ - 本質的な「飽和」からの価値
-
05-10-2019 - |
質問
2つの大きな(100000以上の要素)ベクトルの内部積を計算するためのSSE内シンシクスを実装する簡単なプログラムを作成しました。このプログラムは、両方の実行時間を比較し、内製品は従来の方法と内在性を使用して計算しました。内側の製品を計算するステートメントの前に、私が内側ループを挿入するまで、すべてが正常に機能します。さらに先に進む前に、コードがあります。
//this is a sample Intrinsics program to compute inner product of two vectors and compare Intrinsics with traditional method of doing things.
#include <iostream>
#include <iomanip>
#include <xmmintrin.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
using namespace std;
typedef float v4sf __attribute__ ((vector_size(16)));
double innerProduct(float* arr1, int len1, float* arr2, int len2) { //assume len1 = len2.
float result = 0.0;
for(int i = 0; i < len1; i++) {
for(int j = 0; j < len1; j++) {
result += (arr1[i] * arr2[i]);
}
}
//float y = 1.23e+09;
//cout << "y = " << y << endl;
return result;
}
double sse_v4sf_innerProduct(float* arr1, int len1, float* arr2, int len2) { //assume that len1 = len2.
if(len1 != len2) {
cout << "Lengths not equal." << endl;
exit(1);
}
/*steps:
* 1. load a long-type (4 float) into a v4sf type data from both arrays.
* 2. multiply the two.
* 3. multiply the same and store result.
* 4. add this to previous results.
*/
v4sf arr1Data, arr2Data, prevSums, multVal, xyz;
//__builtin_ia32_xorps(prevSums, prevSums); //making it equal zero.
//can explicitly load 0 into prevSums using loadps or storeps (Check).
float temp[4] = {0.0, 0.0, 0.0, 0.0};
prevSums = __builtin_ia32_loadups(temp);
float result = 0.0;
for(int i = 0; i < (len1 - 3); i += 4) {
for(int j = 0; j < len1; j++) {
arr1Data = __builtin_ia32_loadups(&arr1[i]);
arr2Data = __builtin_ia32_loadups(&arr2[i]); //store the contents of two arrays.
multVal = __builtin_ia32_mulps(arr1Data, arr2Data); //multiply.
xyz = __builtin_ia32_addps(multVal, prevSums);
prevSums = xyz;
}
}
//prevSums will hold the sums of 4 32-bit floating point values taken at a time. Individual entries in prevSums also need to be added.
__builtin_ia32_storeups(temp, prevSums); //store prevSums into temp.
cout << "Values of temp:" << endl;
for(int i = 0; i < 4; i++)
cout << temp[i] << endl;
result += temp[0] + temp[1] + temp[2] + temp[3];
return result;
}
int main() {
clock_t begin, end;
int length = 100000;
float *arr1, *arr2;
double result_Conventional, result_Intrinsic;
// printStats("Allocating memory.");
arr1 = new float[length];
arr2 = new float[length];
// printStats("End allocation.");
srand(time(NULL)); //init random seed.
// printStats("Initializing array1 and array2");
begin = clock();
for(int i = 0; i < length; i++) {
// for(int j = 0; j < length; j++) {
// arr1[i] = rand() % 10 + 1;
arr1[i] = 2.5;
// arr2[i] = rand() % 10 - 1;
arr2[i] = 2.5;
// }
}
end = clock();
cout << "Time to initialize array1 and array2 = " << ((double) (end - begin)) / CLOCKS_PER_SEC << endl;
// printStats("Finished initialization.");
// printStats("Begin inner product conventionally.");
begin = clock();
result_Conventional = innerProduct(arr1, length, arr2, length);
end = clock();
cout << "Time to compute inner product conventionally = " << ((double) (end - begin)) / CLOCKS_PER_SEC << endl;
// printStats("End inner product conventionally.");
// printStats("Begin inner product using Intrinsics.");
begin = clock();
result_Intrinsic = sse_v4sf_innerProduct(arr1, length, arr2, length);
end = clock();
cout << "Time to compute inner product with intrinsics = " << ((double) (end - begin)) / CLOCKS_PER_SEC << endl;
//printStats("End inner product using Intrinsics.");
cout << "Results: " << endl;
cout << " result_Conventional = " << result_Conventional << endl;
cout << " result_Intrinsics = " << result_Intrinsic << endl;
return 0;
}
次のG ++呼び出しを使用してこれを構築します。
g++ -W -Wall -O2 -pedantic -march=i386 -msse intrinsics_SSE_innerProduct.C -o innerProduct
上記の各ループは、両方の関数で、合計n^2回実行されます。ただし、ARR1とARR2(2つのフローティングポイントベクトル)に値2.5がロードされているため、アレイの長さは100,000であるため、両方の場合の結果は6.25E+10でなければなりません。私が得た結果は次のとおりです。
結果:
result_conventional = 6.25e+10
result_intrinsics = 5.36871e+08
これだけではありません。内因性を使用する関数から返された値は、上記の値で「飽和」になるようです。アレイの要素とさまざまなサイズに他の値を配置してみました。しかし、配列の内容と1000を超える任意のサイズの1.0を超える値は、上記の同じ値を満たしているようです。
当初、SSE内のすべての操作が浮動小数点にあるためだと思っていましたが、浮動点はE+08のオーダーである数値を保存できるはずです。
私はどこで間違っているのかを見ようとしていますが、それを理解することはできません。 G ++バージョン:G ++(GCC)4.4.1 20090725(Red Hat 4.4.1-2)を使用しています。
これに関する助けは大歓迎です。
ありがとう、
スリラム。
解決
あなたが抱えている問題は、 float
6.25e+10を保存できますが、精度の2桁しかありません。
つまり、一度に少しだけ少数を一緒に追加することで多数を構築している場合、少数の数字が少ない数字よりも少ない数が少ないポイントに達し、追加することは効果がありません。
なぜあなたがこの動作を非主導的なバージョンで取得していないのか、それはおそらく result
変数は、フロートの実際のストレージをより高い精度を使用して、より高い精度を使用するため、登録簿に保持されています。 float
ループのすべての反復で。生成されたアセンブラーコードを確認する必要があります。