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