g++ SSE内置函数困境 - 内置函数的值被"饱和"

5

我编写了一个简单的程序,用于实现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,数组的长度为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)。
如果有任何帮助,将不胜感激。
谢谢, Sriram。
1个回答

5
你遇到的问题是,虽然float可以存储6.25e+10,但它只有几个有效数字。
这意味着当你通过逐步添加许多小数来构建一个大数时,会达到一个点,其中较小的数字比较大数中最低精度数字还要小,因此将其相加没有任何效果。
至于为什么在非内在版本中没有出现这种行为,很可能是result变量被保存在使用比float实际存储更高精度的寄存器中,因此它在循环的每次迭代中都没有被截断为float的精度。你必须查看生成的汇编代码才能确定。

但是,如果我刪除內部循環 ( (for int j = 0; j < len1; j++)),不同的數組長度 (對於這兩個函數) 都能得到正確的答案。我上面提到的問題只會在我有內部循環時出現。 此外,如果小數相加造成精度損失是問題所在,那麼當我在沒有內部循環的情況下嘗試進行程序時,它應該已經出現了。2.5(和 2.5^2)的數組值並不小到會出現精度損失。如果我錯了,請指正我。 - Sriram
重新阅读他的回答。他说当值已经很大时,就会出现这种情况。因此,如果您最初使用的是2.5e08,则添加2.5可能不会有任何区别。您应该尝试用double替换并查看是否有差异。 - Puppy
1
你改变了哪个“result”?如果是非内在版本,那我预计没有变化 - 它已经正确了。在内在版本中,这没有影响,因为你正在使用“v4sf”作为关键中间存储。 - CB Bailey
我在两个函数中更改了result的返回类型。 - Sriram
我该如何查找生成的汇编代码?我需要寻找什么? - Sriram
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接