5

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

これだけではありません。組み込み関数を使用した関数から返される値は、上記の値で「飽和」しているようです。配列の要素と異なるサイズに他の値を入れてみました。しかし、配列の内容の 1.0 を超える値と 1000 を超えるサイズは、上記と同じ値になるようです。

最初は、SSE 内のすべての操作が浮動小数点で行われるためではないかと考えましたが、浮動小数点は e+08 のオーダーの数値を格納できるはずです。

どこが間違っている可能性があるかを確認しようとしていますが、それを理解できないようです。g++ バージョンを使用しています: g++ (GCC) 4.4.1 20090725 (Red Hat 4.4.1-2)。

これに関するヘルプは大歓迎です。

ありがとう、
スリラム。

4

1 に答える 1

5

あなたが抱えている問題は、 afloatは 6.25e+10 を格納できますが、有効桁数が数桁しかないことです。

これは、小さな数を少しずつ足し合わせて大きな数を作る場合、小さな数が大きな数の中で最も低い精度の桁よりも小さい点に到達するため、足し合わせても効果がないことを意味します。

非組み込みバージョンでこの動作が得られない理由についてはresult、浮動小数点の実際のストレージよりも高い精度を使用するレジスタに変数が保持されている可能性が高いため、変数の精度に切り捨てられていません。floatループの繰り返しごとに。確認するには、生成されたアセンブラー コードを確認する必要があります。

于 2010-06-01T06:41:44.763 に答える