【问题标题】:Why is using a Tuple faster than a List in this example?为什么在这个例子中使用 Tuple 比 List 快?
【发布时间】:2016-11-24 01:10:21
【问题描述】:

我写了一个 C# 类,它用一些数据填充“双打列表”(不管数据是什么,现在它可能只是一些垃圾:)),用于测试目的:

代码如下:

    class test
    {
    public test()
    {
       _myListOfList = new List<List<double>>(1000000);
    }
    public void Run()
    {
        for (int i = 0; i < _myListOfList.Capacity; i++)
        {
            _myListOfList.Add(
                new List<double>(3) { i, 10*i, 100*i}
                ); //Populate the list with data
        }
    }

    private List<List<double>> _myListOfList;
}

我将此代码的执行速度与以下内容进行了比较:(将双精度列表替换为元组)

    class test
    {
    public test()
    {
       _myListOfTuple = new List<Tuple<double, double, double>>(1000000);
    }
    public void Run()
    {
        for (int i = 0; i < _myListOfTuple.Capacity; i++)
        {
            _myListOfTuple.Add(
                new Tuple<double, double, double>(i, 10 * i, 100 * i)
                ); //Populate the list with data
        }
    }

    private List<Tuple<double, double, double>> _myListOfTuple;
}

事实证明,使用元组似乎要快得多。我针对不同的列表大小(从 200,000 个元素 -> 列表中的 500 万个元素)运行了这段代码,这是我得到的结果:

我无法真正理解这一点。我怎么会有这么大的差异?使用存储相同类型对象的元组(此处为双精度)没有多大意义。我宁愿使用列表/数组来做到这一点:我做错了什么?有没有办法让案例#1 的运行速度与案例#2 一样快/快?

谢谢!

【问题讨论】:

  • 为什么这令人惊讶?处理存储任意数量的对象比存储恰好存储 3 个对象需要更多的工作。
  • 开销。一方面,与列表相比,您希望双精度元组占用多少空间?您希望它们在内部拥有每种类型的哪些字段?
  • 将微小的差异放大到足以使它们看起来很大..

标签: c# list tuples


【解决方案1】:

new Tuple&lt;double, double, double&gt;(i, 10 * i, 100 * i)new List&lt;double&gt;(3) { i, 10*i, 100*i} 之间存在差异。

第一个非常简单 - 只需 3 个作业:

public Tuple(T1 item1, T2 item2, T3 item3) {
    m_Item1 = item1;
    m_Item2 = item2;
    m_Item3 = item3;
}

第二个实际上被编译器转化为3个Add方法调用:

var temp = new List<double>(3);
temp.Add(i);
temp.Add(10 * i);
temp.Add(100 * i);

Add 不仅仅是一个任务:

public void Add(T item) {
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size++] = item;
    _version++;
}

要运行的代码越多,执行越慢。很简单..

【讨论】:

  • 非常感谢您的回答.. 这很有意义!正如@Tigran 建议的那样,我用一个数组(new int[3] { i, 10 * i, 100 * i })替换了 List 并且在速度方面我得到了非常相似的结果:)
【解决方案2】:

正如@Marcin 的回答中提到的那样,即使通过初始化列表IL 初始化List&lt;T&gt;,内部仍然有Add() 函数,即使您最初在构造期间指定Capacity 列表也是如此。就像您在示例中所做的那样。

有没有办法让案例 #1 的运行速度与案例 #2 一样快/快?

可能的解决方案可能是直接分配给成员:

list[0] = 
list[1] = 
list[2] = 

在这种情况下,IL 看起来像这样:

IL_0000:  ldc.i4.3    
IL_0001:  newobj      System.Collections.Generic.List<System.Double>..ctor
IL_0006:  stloc.0     // list
IL_0007:  ldloc.0     // list
IL_0008:  ldc.i4.0    
IL_0009:  ldc.r8      00 00 00 00 00 00 F0 3F 
IL_0012:  callvirt    System.Collections.Generic.List<System.Double>.set_Item
IL_0017:  ldloc.0     // list
IL_0018:  ldc.i4.1    
IL_0019:  ldc.r8      00 00 00 00 00 00 24 40 
IL_0022:  callvirt    System.Collections.Generic.List<System.Double>.set_Item
IL_0027:  ldloc.0     // list
IL_0028:  ldc.i4.2    
IL_0029:  ldc.r8      00 00 00 00 00 00 59 40 
IL_0032:  callvirt    System.Collections.Generic.List<System.Double>.set_Item
IL_0037:  ret  

set_Item 更快,因为它是一个简单的分配。

或者,使用简单的Array。性能应该更好。尽管如此,对于诸如 A vs B 速度之类的事情,真正的答案只有在具体测量之后才能得到。

【讨论】:

    猜你喜欢
    • 2021-05-31
    • 2013-06-13
    • 2015-01-07
    • 2015-07-24
    • 1970-01-01
    • 2015-04-16
    相关资源
    最近更新 更多