【问题标题】:Fastest way to move a part of an array to the right将数组的一部分向右移动的最快方法
【发布时间】:2010-11-19 14:02:03
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: .net performance arrays


    【解决方案1】:

    有两种明显的方法:使用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&lt;T&gt; 插入使用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
    

    也就是说,对于intDateTimeElementByElement 明显更快;而对于stringBuiltInCopy 的速度是ElementByElement 的两倍多(对于int,速度是ElementByElement 的两倍)。我希望 intstring 在 32 位机器上的结果非常相似,因为堆栈上对 string 的引用与 int 的大小相同,并且除了读取之外没有其他操作并且应该涉及写入堆栈内存。

    【讨论】:

    • 也许有很多拳击正在进行? (使用值类型的数组比使用引用类型的数组慢...)
    • 在这种情况下,我希望int 上的Array.Copystring 慢,但事实并非如此。
    【解决方案2】:

    在这种情况下,也许使用linked list 会更好。

    【讨论】:

    • 除非他还需要随机访问
    • 您可以访问链表中的任意元素,尽管需要更多时间。
    【解决方案3】:

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

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

    我已经对此进行了基准测试,它的速度大约是您的 for 循环解决方案的两倍。

    【讨论】:

    • OP 说的是一个小数组。很难在 1-2 行缓存行中击败线性副本。
    • 我的基准测试结果非常不同:在 int 上无法区分(1,01 vs 1,00),在字符串上稍快(4,11 vs 4,20),稍慢在日期时间(2,00 对 1,70)。
    • YMMV。在我的机器上,它始终更快(Intel Xeon E7730,.Net 3.5 SP1)。我只是在对一个 int 数组进行基准测试。
    【解决方案4】:

    List&lt;T&gt; 为例 - 它使用Array.Copy,这表明如果您被限制使用数组,那么这可能确实是您的最佳选择。

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

    • 如果您总是在 0 处插入,请使用 List&lt;T&gt;(或保留您自己的阵列以备不时之需,但我想不出为什么),但是倒过来想一想——所以“插入”变成了“追加”,这样更便宜(特别是如果你不必重新分配)
    • 链表是一种选择
    • 队列/堆栈也是如此(取决于您是否/如何删除数据)
    • 或者对于前置和附加,在两边保留一个具有备用容量的数组,包裹在类似List&lt;T&gt;的东西中(但比它更复杂) - 并保持偏移量 - 即你有一个 100 的数组,知道你的“零”是 50,你已经使用了 20;在“零”处插入然后简单地更改偏移量 49 处的值,将主零偏移量减少到 49,并将虚拟长度增加到 21

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2013-11-05
        • 1970-01-01
        • 2015-07-27
        • 2011-01-23
        • 2012-09-08
        • 1970-01-01
        • 1970-01-01
        • 2013-12-21
        相关资源
        最近更新 更多