C# 双打有可能比浮球快2倍吗?

C# 双打有可能比浮球快2倍吗?,c#,performance,intel,processor,C#,Performance,Intel,Processor,我进行了一些基准测试来比较双打和浮动的性能。我很惊讶地看到双打比花车快得多 我看到一些关于这方面的讨论,例如: 大多数人表示,由于双精度优化等原因,双精度和浮点性能可能相似。但是当我使用双打时,我看到了x2性能的提升!!怎么可能呢?最糟糕的是,根据一些帖子,我使用的是一个32位的机器,它在浮点运算中的性能确实会更好 我用C++来精确地检查它,但是我看到类似的C++实现也有类似的行为。 我用来检查它的代码: static void Main(string[] args) { double[

我进行了一些基准测试来比较双打和浮动的性能。我很惊讶地看到双打比花车快得多

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

大多数人表示,由于双精度优化等原因,双精度和浮点性能可能相似。但是当我使用双打时,我看到了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;
          }
        }
      }
    }
  }
}
static void Main(字符串[]args)
{
double[,]doubles=新的double[64,64];
浮动[,]浮动=新浮动[64,64];
System.Diagnostics.Stopwatch s=新的System.Diagnostics.Stopwatch();
s、 重启();
双打(双打);
s、 停止();
长加倍时间=s.ElapsedMilliseconds;
s、 重启();
CalcFloats(浮动);
s、 停止();
长浮动时间=s.ElapsedMilliseconds;
Console.WriteLine(“双倍时间:+doubleTime+“毫秒”);
Console.WriteLine(“浮动时间:+floatTime+“毫秒”);
}
专用静态无效计算单元(双[,]arr)
{
不安全的
{
固定(双*p=arr)
{
对于(int b=0;b<192*12;++b)
{
对于(int i=0;i<64;++i)
{
对于(int j=0;j<64;++j)
{
double*addr=(p+i*64+j);
双arrij=*addr;
arrij=arrij==0?1.0f/(i*j):arrij*(双)i/j;
*addr=arrij;
}
}
}
}
}
}
专用静态无效CalcFloat(浮动[,]arr)
{
不安全的
{
固定(浮动*p=arr)
{
对于(int b=0;b<192*12;++b)
{
对于(int i=0;i<64;++i)
{
对于(int j=0;j<64;++j)
{
float*addr=(p+i*64+j);
浮点数arrij=*addr;
arrij=arrij==0-1.0f/(i*j):arrij*(浮动)i/j;
*addr=arrij;
}
}
}
}
}
}
我使用的是一款非常脆弱的笔记本电脑:Intel Atom N455处理器(双核,1.67GHz,32位),内存为2GB。

来自C#规范:

浮点运算的执行精度可能高于 操作的结果类型。例如,一些硬件 体系结构支持“扩展”或“长双精度”浮点 类型的范围和精度大于双精度类型,以及 使用此更高版本隐式执行所有浮点操作 精密型。只有在性能成本过高的情况下才能实现这一目标 硬件架构可以用来执行浮点运算 精度较低,而不需要实现 放弃性能和精度,C#允许更高的精度 用于所有浮点操作的类型。除了 提供更精确的结果,很少有任何可测量的结果 影响。但是,在x*y/z形式的表达式中 乘法产生的结果超出双精度范围,但 随后的除法将临时结果带回 双范围,即表达式以更高的 范围格式可能导致生成有限结果,而不是 无限

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

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


这看起来抖动优化器将球落在了这里,它不会抑制浮点情况下的冗余存储。热代码是
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
我把多余的指令标上了“多余的”。优化器成功地在双版本中消除了它们,从而使代码运行得更快

冗余存储实际上存在于C#编译器生成的IL中,优化器的任务是检测并删除它们。值得注意的是,x86和x64抖动都有此缺陷,因此它看起来像是优化器算法中的一个普遍疏忽

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

请注意,x64代码的运行速度明显快于x86代码,因此务必将平台目标设置为AnyCPU,以获得简单的胜利。这种速度的提高至少有一部分是优化器在提升整数乘法方面的智慧

请确保测试真实数据,由于未初始化的数组内容,您的测量基本上是无效的。对于元素中的非零数据,差异就不那么明显了,这使得除法的成本更高


还要注意双格中的错误,您不应该在双格中使用1.0f。

谢谢您的回复。请注意,在double和float情况下,计数器都以相同的偏移量递增(它是在for循环中手动计算的)。您不需要不安全的代码来复制它。如果您将赋值移回数组中,效果就会消失。您是对的,在我的机器上也会发生这种情况。为什么会这样?可能是通过策略缓存写回/写回(如前所述)?@ZongZhengLi,支持正在执行计算的假设