代码之家  ›  专栏  ›  技术社区  ›  Nick Randell

为什么64位的速度比32位快?

  •  20
  • Nick Randell  · 技术社区  · 16 年前

    我一直在做一些性能测试,主要是为了了解迭代器和简单for循环之间的区别。作为这项工作的一部分,我创建了一组简单的测试,然后对结果完全感到惊讶。对于某些方法,64位的速度几乎是32位的10倍。

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

    下面是有问题的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 < arrayLength; i++)
    {
        array[i] = i;
    }
    
    Console.WriteLine("For: {0}", AverageRunTime(repeat, () => 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);
    }
    

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

    For: 00:00:00.0006080
    For2: 00:00:00.0005694
    Iter: 00:00:00.0001717

    64位

    For: 00:00:00.0007421
    For2: 00:00:00.0000814
    Iter: 00:00:00.0000818

    我从中了解到的另一件事是,在数组上进行迭代与for循环一样高效,而且代码更干净、更易于阅读!

    5 回复  |  直到 16 年前
        1
  •  51
  •   Mehrdad Afshari    16 年前

    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 弗雷奇 如果不需要更改循环中的数组元素。除此之外,运行库还识别该模式 for (int i = 0; i < a.Length; ++i) 并忽略循环内数组访问的绑定检查。这在未来不会发生 LongLength 长的 变量与 长的 ,32位性能将进一步降低。

        2
  •  5
  •   Jesse C. Slicer    16 年前

        3
  •  2
  •   No Refunds No Returns    16 年前

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

        4
  •  1
  •   Thorsten S.    16 年前

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

    1. 一长串的结果。

    对于您需要的64位长 32位上的寄存器,我们只有四个寄存器,所以很有可能所有变量都不能存储在寄存器中,但必须存储在堆栈之类的中间存储器中。仅此一项就将大大降低访问速度。

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

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

    将失去相当大的速度。一个数量级的速度增益是GPU仍然喜欢浮点(32位)而不是双精度(64位)的原因。

        5
  •  0
  •   Mike Dunlavey    16 年前

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

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