C#Array / List性能差异

时间:2014-04-27 21:01:12

标签: c# performance

考虑以下方法:

static Stopwatch ArrayTest(int size)
{
    var arr = new int[size];
    Stopwatch stw = new Stopwatch();
    stw.Start();
    for (int i = 0; i < size; i++)
    {
        arr[i] = i / div;
    }
    var rnd = new Random(1);
    var sz2 = size / div;
    for (int i = 0; i < sz2; i++)
    {
        var sz = size - i;
        var ix = rnd.Next(sz);
        Array.Copy(arr, ix + 1, arr, ix, size - ix - 1);
        arr[sz - 1] = 0;
    }
    double sum = 0.0;
    for (int i = 0; i < size - sz2; i++)
    {
        sum += arr[i];
    }
    stw.Stop();
    Console.Write("     Array: {0}", sum);
    return stw;
}

static Stopwatch ListTest(int size)
{
    var lst = new List<int>();
    Stopwatch stw = new Stopwatch();
    stw.Start();
    for (int i = 0; i < size; i++)
    {
        lst.Add(i / div);
    }
    var rnd = new Random(1);
    var sz2 = size / div;
    for (int i = 0; i < sz2; i++)
    {
        var ix = rnd.Next(lst.Count);
        lst.RemoveAt(ix);
    }
    double sum = 0.0;
    for (int i = 0; i < lst.Count; i++)
    {
        sum += lst[i];
    }
    stw.Stop();
    Console.Write("      List: {0}", sum);
    return stw;
}

div = 2且size = 200000。 运行此(在发布时编译)会产生以下结果:

 Array: 5012641699  12.8367529 s
  List: 5012641699   6.1027289 s

根据http://referencesource.microsoft.com/#mscorlib List.RemoveAt实现如下:

    // Removes the element at the given index. The size of the list is
    // decreased by one.
    // 
    public void RemoveAt(int index) {
        if ((uint)index >= (uint)_size) {
            ThrowHelper.ThrowArgumentOutOfRangeException();
        }
        Contract.EndContractBlock();
        _size--;
        if (index < _size) {
            Array.Copy(_items, index + 1, _items, index, _size - index);
        }
        _items[_size] = default(T);
        _version++;
    }

所以我想知道为什么ArrayTest需要的时间是ListTest的两倍。在我看来,他们都做了几乎相同的事情,实际上我希望ArrayTest由于更少的开销而更快。也许我错过了一些明显的东西?

更新 让我解释一下这个例子。我们的想法是在随机删除条件下测量性能(同时仍然保留索引访问)。每个方法的第一部分初始化一个数组/列表,其中数组中的每个元素是索引的1/2(整数除法)。下一部分随机删除1/2元素。最后一个循环简单地将数组/列表中剩余的值相加,用作校验和(用于比较结果)。对代码进行概要分析表明,在ArrayTest中,Array.Copy使用的时间最多,而在ListTest中,lst.RemoveAt使用的时间最多。

我将尝试在更简单的示例中复制行为并在此处进行更新。

2 个答案:

答案 0 :(得分:0)

您的测试计划存在缺陷。你已经让它过于复杂,因而很难在纸上证明。找出代码所做的唯一方法是通过调试,手工计算所有内容。

所以我做了一个更简单的测试。

void Main()
{
    const int LENGTH = 200000;

    Stopwatch sw = Stopwatch.StartNew();
    var a = new int[LENGTH];
    for (int index = LENGTH-1; index > 0; index--)
        Array.Copy(a, 1, a, 0, index);
    sw.Stop();
    sw.ElapsedMilliseconds.Dump();

    var l = new List<int>(a);

    sw = Stopwatch.StartNew();
    for (int index = LENGTH-1; index > 0; index--)
        l.RemoveAt(0);
    sw.Stop();
    sw.ElapsedMilliseconds.Dump();
}

它只是“删除”数组的第0个元素和列表。我机器上的结果是:

3366
3442

3270
3242

3343
3385

那是3次跑。

答案 1 :(得分:0)

问题似乎在于:

Array.Copy(arr, ix + 1, arr, ix, size - ix - 1);
方法ArrayTest中的

。它应该读

Array.Copy(arr, ix + 1, arr, ix, sz - ix - 1);

因为在调整数组大小时不改变变量size,而是在每次迭代中计算实际大小sz