按位和按键的性能在64位上的多头vs整数

时间:2011-12-20 05:03:14

标签: c# .net

似乎在两个&之间执行long操作时,它需要与4个32位int内的等效操作相同的时间。

例如

long1 & long2

只要

int1 & int2
int3 & int4

这是在64位操作系统上运行,目标是64位.net。

理论上,这应该快两倍。有没有人以前遇到过这个?

修改

作为简化,假设我有两个64位数据。我取64位并将它们放入long,然后对这两位执行按位&

我还获取这两组数据,并将64位放入两个32位int值并执行两个 & s。我希望看到long &操作比int &操作运行得更快。

2 个答案:

答案 0 :(得分:6)

我无法重现这个问题。

我的测试如下(显示的是int版本):

// deliberately made hard to optimise without whole program optimisation
public static int[] data = new int[1000000]; // long[] when testing long

// I happened to have a winforms app open, feel free to make this a console app..
private void button1_Click(object sender, EventArgs e)
{
    long best = long.MaxValue;
    for (int j = 0; j < 1000; j++)
    {
        Stopwatch timer = Stopwatch.StartNew();
        int a1 = ~0, b1 = 0x55555555, c1 = 0x12345678; // varies: see below
        int a2 = ~0, b2 = 0x55555555, c2 = 0x12345678;
        int[] d = data; // long[] when testing long
        for (int i = 0; i < d.Length; i++)
        {
            int v = d[i]; // long when testing long, see below
            a1 &= v; a2 &= v;
            b1 &= v; b2 &= v;
            c1 &= v; c2 &= v;
        }
        // don't average times: we want the result with minimal context switching
        best = Math.Min(best, timer.ElapsedTicks); 
        button1.Text = best.ToString() + ":" + (a1 + a2 + b1 + b2 + c1 + c2).ToString("X8");
    }
}

为了测试多头a1a2等合并,给出:

long a = ~0, b = 0x5555555555555555, c = 0x1234567812345678;

在我的笔记本电脑上运行这两个程序(i7 Q720)作为VS(.NET 4.5)的外部版本构建我得到以下时间:

int: 2238,长: 1924

现在考虑到有大量的循环开销,并且long版本正在使用两倍的数据(8mb对4mb),它仍然明显领先。所以我没有理由相信C#没有充分利用处理器的64位bitops。

但我们真的不应该把它放在第一位。如果有问题,只需检查jited代码(Debug - &gt; Windows - &gt; Disassembly)。确保编译器使用您期望它使用的指令,然后继续。

尝试在处理器上测量那些单独指令的性能(这可能是你的处理器模型特有的),除了汇编程序之外的任何东西都是一个非常糟糕的主意 - 并且从像C#这样的jit编译语言中,超越徒劳。但是无论如何都没有必要,因为如果你需要知道它就在Intel's optimisation handbook。{/ p>

为此,这里是x64上程序的a &=版本的long的反汇编(发布,但在调试器内部 - 不确定这是否会影响程序集,但它肯定会影响到性能):

00000111  mov         rcx,qword ptr [rsp+60h] ; a &= v
00000116  mov         rax,qword ptr [rsp+38h] 
0000011b  and         rax,rcx 
0000011e  mov         qword ptr [rsp+38h],rax 

正如您所看到的,有一个64位和预期的操作,以及三个64位移动。到目前为止一直很好,而int版本的操作数量只有一半:

00000122  mov         ecx,dword ptr [rsp+5Ch] ; a1 &= v
00000126  mov         eax,dword ptr [rsp+38h] 
0000012a  and         eax,ecx 
0000012c  mov         dword ptr [rsp+38h],eax 
00000130  mov         ecx,dword ptr [rsp+5Ch] ; a2 &= v
00000134  mov         eax,dword ptr [rsp+44h] 
00000138  and         eax,ecx 
0000013a  mov         dword ptr [rsp+44h],eax 

我只能得出结论,您所看到的问题特定于您的测试套件,构建选项,处理器...或者很可能,&不是您认为的争论点它是。 HTH。

答案 1 :(得分:5)

我无法重现你的时间。以下代码生成两个数组:一个1,000,000个long,一个具有2,000,000个int。然后它循环遍历数组,将&运算符应用于连续值。它保持运行总和并输出它,只是为了确保编译器不会决定完全删除循环,因为它没有做任何事情。

经过几十次连续运行,long循环的速度至少是int循环的两倍。这是在带有Windows 8开发人员预览版和Visual Studio 11开发人员预览版的Core 2 Quad上运行的。程序使用“Any CPU”编译,并以64位模式运行。所有测试均使用Ctrl + F5完成,以便不涉及调试器。

        int numLongs = 1000000;
        int numInts = 2*numLongs;
        var longs = new long[numLongs];
        var ints = new int[numInts];
        Random rnd = new Random();
        // generate values
        for (int i = 0; i < numLongs; ++i)
        {
            int i1 = rnd.Next();
            int i2 = rnd.Next();
            ints[2 * i] = i1;
            ints[2 * i + 1] = i2;
            long l = i1;
            l = (l << 32) | (uint)i2;
            longs[i] = l;
        }

        // time operations.
        int isum = 0;
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < numInts; i += 2)
        {
            isum += ints[i] & ints[i + 1];
        }
        sw.Stop();
        Console.WriteLine("Ints: {0} ms. isum = {1}", sw.ElapsedMilliseconds, isum);

        long lsum = 0;
        int halfLongs = numLongs / 2;
        sw.Restart();
        for (int i = 0; i < halfLongs; i += 2)
        {
            lsum += longs[i] & longs[i + 1];
        }
        sw.Stop();
        Console.WriteLine("Longs: {0} ms. lsum = {1}", sw.ElapsedMilliseconds, lsum);