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

时间:2013-12-09 21:29:39

标签: c# performance intel processor

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

我看到了一些关于此的讨论,例如:

Is using double faster than float?

Are doubles faster than floats in 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;
          }
        }
      }
    }
  }
}

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

2 个答案:

答案 0 :(得分: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

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

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

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

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

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

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

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

答案 1 :(得分:3)

来自C#规范:

  

可以以高于的精度执行浮点运算   操作的结果类型。例如,一些硬件   体系结构支持“扩展”或“长双”浮点   类型比双重类型具有更大的范围和精度,和   使用更高的值隐式执行所有浮点运算   精密型。只有在性能成本过高的情况下才能如此   使硬件架构执行浮点运算   精度较低,而不是需要实现   没有性能和精度,C#允许更高的精度   用于所有浮点运算的类型。以外   提供更精确的结果,这很少有任何可衡量的   效果。但是,在x * y / z形式的表达式中,其中   乘法产生的结果超出双倍范围,但是   随后的分裂将临时结果带回   双范围,表达式评估更高的事实   范围格式可能会导致生成有限结果而不是   无穷大。

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

此外,如accepted answer中所述,您链接到的一个问题是,CLI规范要求在某些其他情况下截断64位(或80位)值。该答案还与此处的其他讨论有关:

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