为什么64位比32位更快?

我一直在做一些性能测试,主要是因为我可以理解迭代器和简单for循环之间的区别。 作为其中的一部分,我创建了一组简单的测试,然后对结果感到惊讶。 对于某些方法,64位比32位快近10倍。

我正在寻找的是为什么会发生这种情况的一些解释。

[下面的答案说明这是由于32位应用程序中的64位算术。 将long更改为int会在32位和64位系统上产生良好的性能。

以下是有问题的3种方法。

private static long ForSumArray(long[] array) { var result = 0L; for (var i = 0L; i < array.LongLength; i++) { result += array[i]; } return result; } private static long ForSumArray2(long[] array) { var length = array.LongLength; var result = 0L; for (var i = 0L; i < length; i++) { result += array[i]; } return result; } private static long IterSumArray(long[] array) { var result = 0L; foreach (var entry in array) { result += entry; } return result; } 

我有一个简单的测试工具来测试这个

 var repeat = 10000; var arrayLength = 100000; var array = new long[arrayLength]; for (var i = 0; i  ForSumArray(array))); repeat = 100000; Console.WriteLine("For2: {0}", AverageRunTime(repeat, () => ForSumArray2(array))); Console.WriteLine("Iter: {0}", AverageRunTime(repeat, () => IterSumArray(array))); private static TimeSpan AverageRunTime(int count, Action method) { var stopwatch = new Stopwatch(); stopwatch.Start(); for (var i = 0; i < count; i++) { method(); } stopwatch.Stop(); var average = stopwatch.Elapsed.Ticks / count; return new TimeSpan(average); } 

当我运行这些时,我得到以下结果:
32位:

 适用于:00:00:00.0006080
 2:00:00:00.0005694
 Iter:00:00:00.0001717 

64位

 适用于:00:00:00.0007421
 2:00:00:00.0000814
 Iter:00:00:00.0000818 

我从中读到的东西是使用LongLength很慢。 如果我使用array.Length,第一个for循环的性能在64位中相当不错,但不是32位。

我从中读到的另一件事是迭代数组与for循环一样高效,代码更清晰,更易于阅读!

x64处理器包含64位通用寄存器,通过它们可以在单个指令中计算64位整数的运算。 32位处理器没有。 这与您的程序特别相关,因为它大量使用long (64位整数)变量。

例如,在x64汇编中,要添加存储在寄存器中的几个64位整数,您可以简单地执行以下操作:

 ; adds rbx to rax add rax, rbx 

要在32位x86处理器上执行相同的操作,您必须使用两个寄存器并在第二个操作中手动使用第一个操作的进位:

 ; adds ecx:ebx to edx:eax add eax, ebx adc edx, ecx 

更多的指令和更少的寄存器意味着更多的时钟周期,内存提取,……这最终会导致性能降低。 数字运算应用程序中的差异非常明显。

对于.NET应用程序,似乎64位JIT编译器执行更积极的优化以提高整体性能。

关于你关于数组迭代的观点,C#编译器非常聪明,可以识别foreach不是数组并特别对它们进行处理。 生成的代码与使用for循环相同,如果您不需要更改循环中的数组元素,建议您使用foreach 。 除此之外,运行时识别for (int i = 0; i < a.Length; ++i)并省略循环内数组访问的绑定检查。 这在LongLength情况下不会发生,并且会导致性能下降(对于32位和64位情况都是如此); 由于你将使用LongLength long变量,32位性能将进一步降低。

long数据类型为64位,在64位进程中,它作为单个本机长度单元处理。 在32位进程中,它被视为2个32位单元。 数学,尤其是这些“分裂”类型将是处理器密集型的。

不确定“为什么”但我会确保在你的定时器循环之外至少调用一次“方法”,这样你就不会计算第一次jitting。 (因为这看起来像C#)。

哦,这很容易。 我假设您使用的是x86技术。 在汇编程序中执行循环需要什么?

  1. 一个索引变量i
  2. 一个结果变量结果
  3. 一系列结果。

所以你需要三个变量。 如果可以将它们存储在寄存器中,则变量访问最快; 如果你需要将它们移入和移出内存,你就会失去速度。 对于64位长,你需要32位的两个寄存器,我们只有四个寄存器,所以很有可能所有变量都不能存储在寄存器中,但必须存储在像堆栈这样的中间存储器中。 仅这一点就会大大减慢访问速度。

增加数字:增加必须是两次; 第一次没有进位,第二次带进位。 它可以在一个周期内完成64位。

移动/加载:对于每个1个周期的64位var,您需要两个周期才能将32位加载/卸载长整数到内存中。

每个组件数据类型(由比寄存器/地址位更多的位组成的数据类型)将失去相当大的速度。 一个数量级的速度增益是GPU仍然更喜欢浮点数(32位)而不是双精度(64位)的原因。

正如其他人所说的那样,在32位机器上进行64位运算需要额外的操作,如果进行乘法或除法则更多。

回到你对迭代器与简单for循环的关注,迭代器可以有相当复杂的定义,只有在内联和编译器优化能够用等效的简单forms替换它们时它们才会很快。 它实际上取决于迭代器的类型和底层容器实现。 判断它是否已被合理优化的最简单方法是检查生成的汇编代码。 另一种方法是将它放在一个长时间运行的循环中,暂停它,然后查看堆栈以查看它正在做什么。