C#最快的移位阵列方式

Des*_*ted 53 c# arrays performance

如何快速将数组中的所有项目向左移动,用null填充结尾?

例如,[0,1,2,3,4,5,6]将成为[1,2,3,4,5,6,null]

编辑:我很快说,但我想我的意思是有效率.我需要在不创建List或其他数据结构的情况下执行此操作.这是我需要在尽可能短的时间内做几十万次的事情.

Jas*_*yon 110

这是我的测试工具......

var source = Enumerable.Range(1, 100).Cast<int?>().ToArray();
var destination = new int?[source.Length];

var s = new Stopwatch();
s.Start();
for (int i = 0; i < 1000000;i++)
{
    Array.Copy(source, 1, destination, 0, source.Length - 1);
}
s.Stop();
Console.WriteLine(s.Elapsed);
Run Code Online (Sandbox Code Playgroud)

以下是每个解决方案100万次迭代的性能结果(8 Core Intel Xeon E5450 @ 3.00GHz)

                       100 elements  10000 elements
For Loop                     0.390s         31.839s 
Array.Copy()                 0.177s         12.496s
Aaron 1                      3.789s         84.082s
Array.ConstrainedCopy()      0.197s         17.658s
Run Code Online (Sandbox Code Playgroud)

自己做出选择:)

  • Array.Copy可能更快,因为它可以做你在C#中无法做的事情.它很可能只是在使用页面表并设置标志以强制写入时进行复制. (7认同)
  • 不.我自己试了一下,得到了同样的结果.我猜`Array.Copy`获胜.Dang,这是一种快速的方法. (4认同)
  • 如果可以,我会给你+100.只有两种方法来处理像这样的性能"问题".A)配置文件并选择最佳,或B)确定您并不真正关心在实际条件下几百毫秒的差异.首先编写代码以便于阅读,然后仅在您需要时进行分析! (2认同)
  • 实际上根据代码,系统运行 C 函数 memmove 来完成大部分繁重的工作。对于所有想了解更多信息的人,请查看 clr/src/vm/comsystem.cpp,并查找 SystemNative::ArrayCopy。这是从 http://referencesource.microsoft.com/#mscorlib/system/array.cs,1a86a6b8b02d0948 调用的 (2认同)

Ben*_*n M 57

最快要做到这一点的方法是使用Array.Copy,这在最终实现使用(类似的memcpy)大容量存储器传输操作:

var oldArray = new int?[] { 1, 2, 3, 4, 5, 6 };
var newArray = new int?[oldArray.Length];
Array.Copy(oldArray, 1, newArray, 0, oldArray.Length - 1);
// newArray is now { 2, 3, 4, 5, 6, null }
Run Code Online (Sandbox Code Playgroud)

编辑:根据文件:

如果sourceArray和destinationArray重叠,则此方法的行为就像在覆盖destinationArray之前将sourceArray的原始值保留在临时位置中一样.

因此,如果您不想分配新数组,则可以为源和目标传递原始数组 - 尽管我认为权衡会稍微慢一点,因为值会通过临时保持位置.

我想,就像在任何此类调查中一样,你应该做一些快速的基准测试.

  • 如果你没有"运行测试",显然你并不关心性能!你也可以使用ToList,Remove,ToArray. (5认同)
  • `Copy`方法中的两个数组可能重叠,因此您不需要创建另一个数组.`Array.Copy(oldArray,1,oldArray,0,oldArray.Length - 1)`以及将最后一个元素设置为`null`也可以. (2认同)

tda*_*nes 11

这是我的解决方案,类似于Task,因为它是一个简单的数组包装器,并且需要花费O(1)时间将数组移到左侧.

public class ShiftyArray<T>
{
    private readonly T[] array;
    private int front;

    public ShiftyArray(T[] array)
    {
        this.array = array;
        front = 0;
    }

    public void ShiftLeft()
    {
        array[front++] = default(T);
        if(front > array.Length - 1)
        {
            front = 0;
        }
    }

    public void ShiftLeft(int count)
    {
        for(int i = 0; i < count; i++)
        {
            ShiftLeft();
        }
    }

    public T this[int index]
    {
        get
        {
            if(index > array.Length - 1)
            {
                throw new IndexOutOfRangeException();
            }

            return array[(front + index) % array.Length];
        }
    }

    public int Length { get { return array.Length; } }
}
Run Code Online (Sandbox Code Playgroud)

通过Jason Punyon的测试代码运行它......

int?[] intData = Enumerable.Range(1, 100).Cast<int?>().ToArray();
ShiftyArray<int?> array = new ShiftyArray<int?>(intData);

Stopwatch watch = new Stopwatch();
watch.Start();

for(int i = 0; i < 1000000; i++)
{
    array.ShiftLeft();
}

watch.Stop();

Console.WriteLine(watch.ElapsedMilliseconds);
Run Code Online (Sandbox Code Playgroud)

无论阵列大小如何,都需要~29ms.


Seb*_*Seb 8

难道你不能使用System.Collections.Generic.Queue而不是数组?

我觉得你需要对丢弃它的值执行操作,因此使用队列似乎更合适:

// dummy initialization
        System.Collections.Generic.Queue<int> queue = new Queue<int>();
        for (int i = 0; i < 7; ++i ) { queue.Enqueue(i); }// add each element at the end of the container

        // working thread
        if (queue.Count > 0)
            doSomething(queue.Dequeue());// removes the last element of the container and calls doSomething on it
Run Code Online (Sandbox Code Playgroud)

  • 我可以吗?你能详细说说吗? (3认同)

Mar*_*ner 7

使用Array.Copy()方法,如

int?[] myArray = new int?[]{0,1,2,3,4};
Array.Copy(myArray, 1, myArray, 0, myArray.Length - 1);
myArray[myArray.Length - 1] = null
Run Code Online (Sandbox Code Playgroud)

Array.Copy可能的方式,微软希望我们能够复制数组元素...


Jef*_*dge 5

如果这是绝对是在一个数组,那么我会推荐最明显的代码可能.

for (int index = startIndex; index + 1 < values.Length; index++)
     values[index] = values[index + 1];
values[values.Length - 1] = null;
Run Code Online (Sandbox Code Playgroud)

这为优化器提供了在安装程序的任何目标平台上找到最佳方法的最大机会.

编辑:

我刚刚借用了Jason Punyon的测试代码,我担心他是对的.Array.Copy赢了!

    var source = Enumerable.Range(1, 100).Cast<int?>().ToArray();
    int indexToRemove = 4;

    var s = new Stopwatch();
    s.Start();
    for (int i = 0; i < 1000000; i++)
    {
        Array.Copy(source, indexToRemove + 1, source, indexToRemove, source.Length - indexToRemove - 1);
        //for (int index = indexToRemove; index + 1 < source.Length; index++)
        //    source[index] = source[index + 1]; 
    }
    s.Stop();
    Console.WriteLine(s.Elapsed);
Run Code Online (Sandbox Code Playgroud)

Array.Copy在我的机器上需要103到150毫秒.

for循环在我的机器上需要269到338毫秒.

  • 它确实取决于数组的大小,在我的机器上:如果它低于 54,那么 for 循环实际上更快 (2认同)

use*_*138 5

任何倾注的灵魂找到这个线程,并即将实施一个评价很高的答案.所有这些都是垃圾,我不知道为什么会这样.也许Dested首先要求新的阵列实现,或者现在已经从问题中删除了.好吧,如果您只是想移动数组而不需要新数组,请参阅tdaines的答案之类的答案.并阅读循环缓冲区/环形缓冲区等内容:http://en.wikipedia.org/wiki/Circular_buffer.不需要移动实际数据.移位数组的性能应该与数组的大小相关联.

  • 环形缓冲区更快,因为它避免了副本,但它不提供相同的API.例如,您无法将环缓冲区传递给需要连续存储的函数,因为数据存储在两个块中. (2认同)

Aar*_*ron 2

你可以这样做:

var items = new int?[] { 0, 1, 2, 3, 4, 5, 6 };  // Your array
var itemList = new List<int?>(items);  // Put the items in a List<>
itemList.RemoveAt(1); // Remove the item at index 1
itemList.Add(null); // Add a null to the end of the list
items = itemList.ToArray(); // Turn the list back into an array
Run Code Online (Sandbox Code Playgroud)

当然,完全摆脱数组并仅使用 List<> 会更有效。然后你可以忘记第一行和最后一行,然后这样做:

var itemList = new List<int?> { 0, 1, 2, 3, 4, 5, 6 };
itemList.RemoveAt(1); // Remove the item at index 1
itemList.Add(null); // Add a null to the end of the list
Run Code Online (Sandbox Code Playgroud)

  • 嘿!托管编程是否完全窃取了思考性能的能力? (5认同)