是否有可能双倍比浮动快x2?

MaM*_*zav 5 c# performance processor intel

我进行了一些基准测试,以比较双打和浮动性能.看到双打比浮球要快得多,我感到非常惊讶.

我看到了一些关于这方面的讨论,例如:

使用比浮动更快的双倍?

是否比c#中的浮点数快一倍?

他们中的大多数人表示,由于双精度优化等原因,双重和浮动性能可能相似.但是当我使用双打时我看到了x2的性能提升 !! 这怎么可能?最糟糕的是,我使用的是32位机器,根据一些帖子的说法,这些机器预计会更好地用于花车......

我使用C#来精确地检查它,但我发现类似的C++实现具有类似的行为.

代码我用来检查它:

static void Main(string[] args)
{
  double[,] doubles = new double[64, 64];
  float[,] floats = new float[64, 64];

  System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

  s.Restart();
  CalcDoubles(doubles);
  s.Stop();
  long doubleTime = s.ElapsedMilliseconds;

  s.Restart();
  CalcFloats(floats);
  s.Stop();
  long floatTime = s.ElapsedMilliseconds;

  Console.WriteLine("Doubles time: " + doubleTime + " ms");
  Console.WriteLine("Floats time: " + floatTime + " ms");
}

private static void CalcDoubles(double[,] arr)
{
  unsafe
  {
    fixed (double* p = arr)
    {
      for (int b = 0; b < 192 * 12; ++b)
      {
        for (int i = 0; i < 64; ++i)
        {
          for (int j = 0; j < 64; ++j)
          {
            double* addr = (p + i * 64 + j);
            double arrij = *addr;
            arrij = arrij == 0 ? 1.0f / (i * j) : arrij * (double)i / j;
            *addr = arrij;
          }
        }
      }
    }
  }
}

private static void CalcFloats(float[,] arr)
{
  unsafe
  {
    fixed (float* p = arr)
    {
      for (int b = 0; b < 192 * 12; ++b)
      {
        for (int i = 0; i < 64; ++i)
        {
          for (int j = 0; j < 64; ++j)
          {
            float* addr = (p + i * 64 + j);
            float arrij = *addr;
            arrij = arrij == 0 ? 1.0f / (i * j) : arrij * (float)i / j;
            *addr = arrij;
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我使用的是非常弱的笔记本电脑:Intel Atom N455处理器(双核,1.67GHz,32位),带2GB RAM.

Han*_*ant 10

这看起来抖动优化器在这里丢球,它不会抑制浮动情况下的冗余存储.热代码是1.0f / (i * j)计算,因为所有数组值都为0. x86抖动生成:

01062928  mov         eax,edx                     ; eax = i
0106292A  imul        eax,esi                     ; eax = i * j
0106292D  mov         dword ptr [ebp-10h],eax     ; store to mem
01062930  fild        dword ptr [ebp-10h]         ; convert to double 
01062933  fstp        dword ptr [ebp-10h]         ; redundant store, convert to float
01062936  fld         dword ptr [ebp-10h]         ; redundant load
01062939  fld1                                    ; 1.0f
0106293B  fdivrp      st(1),st                    ; 1.0f / (i * j)
0106293D  fstp        dword ptr [ecx]             ; arrij = result
Run Code Online (Sandbox Code Playgroud)

x64抖动:

00007FFCFD6440B0  cvtsi2ss    xmm0,r10d           ; (float)(i * j)
00007FFCFD6440B5  movss       xmm1,dword ptr [7FFCFD644118h]  ; 1.0f
00007FFCFD6440BD  divss       xmm1,xmm0           ; 1.0f / (i * j)
00007FFCFD6440C1  cvtss2sd    xmm0,xmm1           ; redundant store 
00007FFCFD6440C5  cvtsd2ss    xmm0,xmm0           ; redundant load
00007FFCFD6440C9  movss       dword ptr [rax+r11],xmm0  ; arrij = result
Run Code Online (Sandbox Code Playgroud)

我用"冗余"标记了多余的指令.优化器确实设法在版本中消除它们,以便代码运行得更快.

冗余存储实际上存在于由C#编译器生成的IL中,优化器的工作是检测和删除它们.值得注意的是,x86和x64抖动都有这个缺陷,所以它看起来像是优化算法中的一般疏忽.

x64代码特别值得注意的是将float结果转换为double然后再转换为float,这表明底层问题是数据类型转换,它不知道如何抑制.你也可以在x86代码中看到它,冗余存储实际上是一个双浮点转换.在x86情况下,消除转换看起来很困难,因此很可能已经泄漏到x64抖动中.

请注意,x64代码的运行速度明显快于x86代码,因此请确保将平台目标设置为AnyCPU以获得简单的胜利.至少部分加速是优化器提升整数乘法的聪明之处.

确保测试实际数据,由于未初始化的数组内容,您的测量基本上无效.对于元素中的非零数据,差异不太明显,这使得除法更加昂贵.

另请注意你在双重案例中的错误,你不应该使用1.0f.


pho*_*oog 3

来自 C# 规范:

\n\n
\n

浮点运算的执行精度可以高于运算结果类型的精度。例如,某些硬件架构支持 \xe2\x80\x9cextended\xe2\x80\x9d 或 \xe2\x80\x9clong double\xe2\x80\x9d 浮点\n 类型,其范围和精度比 double 更大类型,并使用此较高精度类型隐式执行所有浮点运算。只有在性能方面付出过高的代价,才能使此类硬件架构以较低的精度执行浮点运算,而不是要求实现同时牺牲性能和精度,C# 允许更高精度的类型用于所有浮点运算。除了提供更精确的结果之外,这很少有任何可测量的效果。但是,在 x * y / z 形式的表达式中,\n 乘法产生的结果超出了 double 范围,但\n 随后的除法将临时结果带回到\n double 范围内,事实上以较高范围格式计算表达式可能会导致产生有限结果而不是无穷大。

\n
\n\n

在将值存储到数组之前,可能需要额外的指令将其转换为 32 位浮点数。

\n\n

此外,正如您链接到的问题之一的接受答案中提到的,CLI 规范要求在某些其他情况下截断 64 位(或 80 位)值。该答案还链接到此处的其他讨论:

\n\n

http://weblog.ikvm.net/PermaLink.aspx?guid=f300c4e1-15b0-45ed-b6a6-b5dc8fb8089e

\n