4

我正在尝试优化通用列表算术运算。我有如下定义的 3 个可空双精度列表。

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();
List<double?> listResult = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;

for (int index = 0; index < recordCount; index++)
{
      double? result = list1[index] + list2[index];
      listResult.Add(result);
}

如果我有大量列表,有什么方法可以让这个操作运行得更快?

感谢您的输入。

4

5 回答 5

9

如果我有大量列表,有什么方法可以让这个操作运行得更快?

您可以将结果列表创建移动到计数之后:

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;
List<double?> listResult = new List<double?>(recordCount);

这将使您可以指定结果所需的确切容量,并避免在列表本身内重新分配。对于“巨大的列表”,这可能是最慢的部分之一,因为随着列表变大,内存分配和复制将是这里最慢的操作。

此外,如果计算很简单,您可能会使用多个核心:

List<double?> list1 = new List<double?>();
List<double?> list2 = new List<double?>();

int recordCount = list1.Count > list2.Count ? list2.Count : list1.Count;

var results = new double?[recordCount]; // Use an array here

Parallel.For(0, recordCount, index => 
    {
        double? result = list1[index] + list2[index];
        results[index] = result;
    });

鉴于“工作”在这里非常简单,您实际上可能需要一个自定义分区器来充分利用并行性(有关详细信息,请参阅如何:加速小型循环体):

var results = new double?[recordCount]; // Use an array here
var rangePartitioner = Partitioner.Create(0, recordCount);

Parallel.ForEach(rangePartitioner, range => 
    {
        for (int index = range.Item1; index < range.Item2; index++)
        {
            results[index] = list1[index] + list2[index];
        }
    });

但是,如果这不是瓶颈,您可以使用 LINQ 单行执行此操作:

var results = list1.Zip(list2, (one, two) => one + two).ToList();

但是,如果性能确实是一个瓶颈,这将(非常轻微地)比自己处理循环效率低。

于 2012-10-05T01:20:15.437 回答
0

如果您提前知道列表的大小,那么简单数组应该运行得更快。像这样创建:

double?[] Array1 = new double?[10];
于 2012-10-05T01:19:29.300 回答
0

您可以做的第一件事是指定结果列表的容量

List<double?> listResult = new List<double?>(recordCount);

这将为每个 List.Add() 调用节省时间的结果预先分配空间。

如果您真的担心性能,您可以将列表分成块并启动多个线程来执行部分结果集,然后在完成后将完整集重新合并在一起。

于 2012-10-05T01:22:32.707 回答
0
var result = from i in
            Enumerable.Range(0, Math.Min(list1.Count, list2.Count))
            select list1.ElementAtOrDefault(i) + list2.ElementAtOrDefault(i);
foreach (var item in result)
{
Debug.Write(item.Value);
}
于 2012-10-05T01:24:19.993 回答
0

如果你有能力使用原始数组而不是列表,你当然可以让它更快 - 多少取决于你想要多低级。纠正了您源代码中的一些错误,我写了三个不同的版本。一种方法是通过为结果创建一个新列表(我冒昧地使用具有容量的构造函数,防止一堆中间分配),从而按照您的代码的方式进行。

我还编写了一个将两个数组相加为第三个的版本,认为剥离 List 会提高效率。

最后,我编写了一个使用不安全代码的版本,使用指针将第一个数组添加到第二个数组。

比较结果如下:

Timings: 500000 iterations of 10000-item lists
  Lists:           00:00:13.9184166
  Arrays (safe):   00:00:08.4868231
  Arrays (unsafe): 00:00:03.0901603

Press any key to continue...

我使用的代码可以在这个 Github gist中找到。

不安全的代码可能有点过于优化了,但是看到这三个示例之间的差异是相当惊人的。为了清楚起见,我建议坚持使用安全代码并使用数组。

于 2012-10-05T01:49:43.403 回答