将阵列的一部分向右移动的最快方法

时间:2009-08-14 08:17:52

标签: .net performance arrays

我需要在一个小数组中“插入”给定索引处的元素。也就是说,将具有较大索引1的所有元素移动到右侧。 .NET中最快的方法是什么?

注意:我添加了自己的答案,但我仍在寻找解释和更快的替代方案。

编辑:我确实需要一个数组,而不是List<T>而不是链表。

更新:由于我没有得到奇怪的效果结果的解释,我已经单独提出了这个问题:Why is copying references to strings much slower than copying ints (but vice versa for Array.Copy())?

5 个答案:

答案 0 :(得分:4)

有两种明显的方法:使用Array.Copy并逐个复制元素:

private static void ElementByElement<T>(T[] arg, int start) {
    for (int i = arg.Length - 1; i > start; i--) {
        arg[i] = arg[i - 1];
    }
}

private static T BuiltInCopy<T>(T[] arg, int start) {
    Array.Copy(arg, start, arg, start + 1, arg.Length - start - 1);
    return arg[0];            
}

一方面,List<T>插入使用Array.Copy。另一方面,Array.Copy是非通用的,可以执行额外工作的批次http://msdn.microsoft.com/en-us/library/z50k9bft.aspx

我希望Array.Copy更快。然而,这次MiniBench测试的结果让我感到惊讶。

internal class Program {
    private static int smallArraySize = 32;

    public static void Main(string[] args) {
        BenchArrayCopy();
    }

    private static void BenchArrayCopy() {
        var smallArrayInt = new int[smallArraySize];
        for (int i = 0; i < smallArraySize; i++)
            smallArrayInt[i] = i;

        var smallArrayString = new string[smallArraySize];
        for (int i = 0; i < smallArraySize; i++)
            smallArrayString[i] = i.ToString();

        var smallArrayDateTime = new DateTime[smallArraySize];
        for (int i = 0; i < smallArraySize; i++)
            smallArrayDateTime[i] = DateTime.Now;

        var moveInt = new TestSuite<int[], int>("Move part of array right by 1: int")
            .Plus(BuiltInCopy, "Array.Copy()")
            .Plus(ElementByElement, "Element by element in a for loop")
            .RunTests(smallArrayInt, 0);

        var moveString = new TestSuite<string[], string>("Move part of array right by 1: string")
            .Plus(BuiltInCopy, "Array.Copy()")
            .Plus(ElementByElement, "Element by element in a for loop")
            .RunTests(smallArrayString, "0");

        var moveDT = new TestSuite<DateTime[], DateTime>("Move part of array right by 1: DateTime")
            .Plus(BuiltInCopy, "Array.Copy()")
            .Plus(ElementByElement, "Element by element in a for loop")
            .RunTests(smallArrayDateTime, smallArrayDateTime[0]);

        moveInt.Display(ResultColumns.All, moveInt.FindBest());
        moveString.Display(ResultColumns.All, moveInt.FindBest());
        moveDT.Display(ResultColumns.All, moveInt.FindBest());
    }

    private static T ElementByElement<T>(T[] arg) {
        int start = 8;
        for (int i = smallArraySize - 1; i > start; i--) {
            arg[i] = arg[i - 1];
        }
        return arg[0];
    }

    private static T BuiltInCopy<T>(T[] arg) {
        int start = 8;
        int length = smallArraySize - start - 1;
        Array.Copy(arg, start, arg, start + 1, length);
        return arg[0];            
    }
}

以下是两次运行的结果:

f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy()                     568475865 0:31.606 1,73
Element by element in a for loop 980013061 0:31.449 1,00

============ Move part of array right by 1: string ============
Array.Copy()                     478224336 0:31.618 2,06
Element by element in a for loop 220168237 0:30.926 4,38

============ Move part of array right by 1: DateTime ============
Array.Copy()                     382906030 0:27.870 2,27
Element by element in a for loop 458265102 0:29.239 1,99


f:\MyProgramming\TimSort\Benchmarks\bin\Release>Benchmarks.exe
============ Move part of array right by 1: int ============
Array.Copy()                     500925013 0:28.514 1,76
Element by element in a for loop 988394220 0:31.967 1,00

============ Move part of array right by 1: string ============
Array.Copy()                     483178262 0:30.048 1,92
Element by element in a for loop 193092931 0:27.642 4,43

============ Move part of array right by 1: DateTime ============
Array.Copy()                     450569361 0:30.807 2,11
Element by element in a for loop 568054290 0:31.385 1,71

也就是说,intDateTime ElementByElement明显更快;而string BuiltInCopy的速度是ElementByElement的两倍(而ElementByElement的速度是int的两倍)。我希望intstring的结果在32位机器上非常相似,因为堆栈上对string的引用与{{1}的大小相同除了读写堆栈内存之外,不应该涉及任何操作。

答案 1 :(得分:2)

在这种情况下,使用linked list可能会更好。

答案 2 :(得分:1)

首先,我会质疑一个数组是否是适合此要求的数据结构选择。但是,我可以在“逐个元素”复制代码中看到可能的优化:

    private static void ElementByElement2<T>(T[] arg, int start)
    {
        int i = arg.Length - 1;
        while (i > start)
            arg[i] = arg[--i];
    }

我对此进行了基准测试,其速度大约是您的for-loop解决方案的两倍。

答案 3 :(得分:1)

List<T>为例 - 它使用Array.Copy,建议如果您被限制使用数组,那么这可能确实是您的最佳选择。< / p>

或者,正如Indeera所说 - 使用不同的容器!根据确切的用法,一些选项

  • 如果您总是在0处插入,请使用List<T>(或使用备用容量保留您自己的阵列,但我不能认为为什么) ,但想想它倒退 - 所以“插入”变成“追加”,这更便宜(特别是如果你不需要重新分配)
  • 链接列表是一个选项
  • 是队列/堆栈(取决于是否/如何删除数据)
  • 或者对于prepend和append,在两个侧保留一个具有备用容量的数组,包含类似(但比List<T>更复杂的东西 - 并保持偏移量 - 即你有一个100的数组,并知道你的“零”是50,你已经使用了20;然后在“零”处插入则简单地更改偏移49处的值,将主零偏移减少到49并将虚拟长度增加到21

答案 4 :(得分:0)

如果要添加到给定索引,则应使用链接列表。另见:What are real world examples of when Linked Lists should be used?