为什么UInt16阵列似乎比int数组更快?

时间:2010-04-13 07:28:54

标签: c# .net arrays clr

似乎C#在添加两个UInt16[]数组时比在添加两个int[]数组时更快。这对我没有意义,因为我会假设数组是字对齐的,因此int[]需要较少的CPU工作,不是吗?

我在下面运行了测试代码,得到了以下结果:

Int    for 1000 took 9896625613 tick (4227 msec)
UInt16 for 1000 took 6297688551 tick (2689 msec)

测试代码执行以下操作:

  1. 一次创建名为ab的两个数组。
  2. 用随机数据填充它们一次。
  3. 启动秒表。
  4. 逐项添加ab。这样做了1000次。
  5. 停止秒表。
  6. 报告花了多长时间。
  7. 这适用于int[] a, bUInt16 a,b。并且每次时间运行代码时,UInt16数组的测试时间比int数组少30%-50%。你能解释一下吗?

    以下是代码,如果您想尝试自己:

    public static UInt16[] GenerateRandomDataUInt16(int length)
    {
        UInt16[] noise = new UInt16[length];
        Random random = new Random((int)DateTime.Now.Ticks);
        for (int i = 0; i < length; ++i)
        {
            noise[i] = (UInt16)random.Next();
        }
    
        return noise;
    }
    
    public static int[] GenerateRandomDataInt(int length)
    {
        int[] noise = new int[length];
        Random random = new Random((int)DateTime.Now.Ticks);
        for (int i = 0; i < length; ++i)
        {
            noise[i] = (int)random.Next();
        }
    
        return noise;
    }
    
    public static int[] AddInt(int[] a, int[] b)
    {
        int len = a.Length;
        int[] result = new int[len];
        for (int i = 0; i < len; ++i)
        {
            result[i] = (int)(a[i] + b[i]);
        }
        return result;
    }
    
    public static UInt16[] AddUInt16(UInt16[] a, UInt16[] b)
    {
        int len = a.Length;
        UInt16[] result = new UInt16[len];
        for (int i = 0; i < len; ++i)
        {
            result[i] = (ushort)(a[i] + b[i]);
        }
        return result;
    }
    
    
    public static void Main()
    {
        int count = 1000;
        int len = 128 * 6000;
    
        int[] aInt = GenerateRandomDataInt(len);
        int[] bInt = GenerateRandomDataInt(len);
    
        Stopwatch s = new Stopwatch();
        s.Start();
        for (int i=0; i<count; ++i) 
        {
            int[] resultInt = AddInt(aInt, bInt);
        }
        s.Stop();
        Console.WriteLine("Int    for " + count 
                    + " took " + s.ElapsedTicks + " tick (" 
                    + s.ElapsedMilliseconds + " msec)");
    
        UInt16[] aUInt16 = GenerateRandomDataUInt16(len);
        UInt16[] bUInt16 = GenerateRandomDataUInt16(len);
    
        s = new Stopwatch();
        s.Start();
        for (int i=0; i<count; ++i) 
        {
            UInt16[] resultUInt16 = AddUInt16(aUInt16, bUInt16);
        }
        s.Stop();
        Console.WriteLine("UInt16 for " + count 
                    + " took " + s.ElapsedTicks + " tick (" 
                    + s.ElapsedMilliseconds + " msec)");
    
    
    }
    

5 个答案:

答案 0 :(得分:6)

你看到的是抽象漏洞。 UInt16占用int的一半内存(16对32位)。

这意味着int16数组占用的内存区占用了int32所占面积的一半。因此,更多的区域可以适应处理器缓存,因此可以非常快速地访问。

您可以在具有更多缓存的处理器上尝试该代码,但差异可能更小。

还尝试使用更大的阵列。

答案 1 :(得分:2)

数组是字对齐的,但没有理由为什么数组中的条目应该是字对齐的。

答案 2 :(得分:1)

只是一个SWAG:UInt16阵列的较小内存使用改善了内存特性(GC,缓存,谁知道还有什么)。由于似乎没有太多的分配,我猜测缓存是主要因素。

此外,你应该注意基准测试可能是一个棘手的业务 - 看起来你的时间可能包括一些JIT编译,这可能会扭曲结果。您可以尝试颠倒使用int数组测试UInt16数组的顺序,并查看时序是否跟随。

Jon Skeet有一个简单的基准框架,当他试图将这些影响考虑在内时,他编写了一个简单的基准框架。我不知道它是否仍然可用(甚至适用);也许他会发表评论。

答案 3 :(得分:1)

几个因素

1)你也计算了结果数组的生成时间。所以看看添加与创建传回的结果数组花了多少时间会很有趣

2)看看IL产生了什么会很有趣。由于您的代码非常简单(迭代和添加),编译器可能正在对此进行优化,可能在多个寄存器中填充多个uint16并在每个指令中执行多次添加

答案 4 :(得分:1)

我不是.NET的专家,但我会检查两件事

  1. 传递较大的数组(类型为int的N个元素)比N ushort元素的数组花费更多时间。这可以使用各种大小的数组和编码风格进行测试 - 请参阅我的评论提问。你的测试中的数字符合这个理论:)。
  2. 添加两个ushort变量可以实现为添加两个int,其结果类型为int - ,无需检查<强>溢出即可。我认为代码中的处理任何类型的异常(包括溢出异常)都是耗时任务。这可以在.NET文档中查看。