g ++ SSE внутренняя дилемма - значение от внутренних "насыщенных" - PullRequest
5 голосов
/ 01 июня 2010

Я написал простую программу для реализации встроенных функций SSE для вычисления внутреннего произведения двух больших (100000 или более элементов) векторов. Программа сравнивает время выполнения как для внутреннего продукта, рассчитанного традиционным способом, так и с использованием встроенных функций. Все работает нормально, пока я не вставлю (просто для удовольствия) внутренний цикл перед оператором, который вычисляет внутренний продукт. Прежде чем идти дальше, вот код:

    //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.5, длина массива равна 100000, результат в обоих случаях должен быть 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).

Любая помощь по этому вопросу приветствуется.

Спасибо
Sriram.

1 Ответ

5 голосов
/ 01 июня 2010

Проблема, с которой вы столкнулись, заключается в том, что, хотя float может хранить 6,25e + 10, он имеет только несколько значащих цифр точности.

Это означает, что когда вы строите большое число путем сложения большого количества маленьких чисел за раз, вы достигаете точки, где меньшее число меньше, чем цифра с наименьшей точностью в большем числе, поэтому сложение без эффекта.

Что касается того, почему вы не получаете такое поведение в не встроенной версии, вполне вероятно, что переменная result хранится в регистре, который использует более высокую точность, чем фактическое хранилище с плавающей точкой, поэтому оно усечено с точностью до float на каждой итерации цикла. Вы должны были бы посмотреть на сгенерированный ассемблерный код, чтобы быть уверенным.

...