回归本源; for循环,数组/向量/列表和优化

时间:2008-12-02 23:26:39

标签: c# arrays optimization list performance

我最近正在研究一些代码,并遇到了一个方法,该方法有3个for循环,可以在2个不同的数组上运行。

基本上,发生的事情是foreach循环将遍历向量并从对象转换DateTime,然后另一个foreach循环将从对象转换长值。每个循环都会将转换后的值存储到列表中。

最后一个循环将遍历这两个列表并将这些值存储到另一个列表中,因为需要对该日期进行一次最终转换。

然后在完成所有操作之后,使用ToArray()将最后两个列表转换为数组。

好的,请耐心等待,我终于回答了我的问题。

所以,我决定制作一个for循环来替换前两个foreach循环并一举转换值(第三个循环是准必要的,但是,我确信有些工作我也可以放它进入单循环)。

然后我读了Gustav Duarte撰写的文章“你的计算机在等待时做了什么”,并开始考虑内存管理以及在for循环中访问数据时正在进行的操作,其中同时访问两个列表。

所以我的问题是,对于这样的事情,最好的方法是什么?尝试压缩for循环,使其尽可能少地循环,从而导致不同列表的多个数据访问。或者,允许多个循环并让系统引入它预期的数据。这些列表和数组可能很大并且循环遍历3个列表,可能是4个,这取决于如何实现ToArray(),可以获得非常高的成本(O(n ^ 3)??)。但是根据我在上述文章和我的CS课程中的理解,不得不提取数据也很昂贵。

有人愿意提供任何见解吗?或者我完全脱掉了摇杆,需要重新学习我没有学过的东西?

谢谢

8 个答案:

答案 0 :(得分:6)

最好的方法?编写最易读的代码,弄清楚其复杂性,并确定是否真的存在问题。

如果你的每个循环都是O(n),那么你仍然只能进行O(n)操作。

话虽如此,听起来像LINQ方法听起来更具可读性......而且效率也可能更高。不可否认,我们还没有看到代码,但我怀疑它是LINQ的理想的东西。

答案 1 :(得分:4)

参考,

文章是在 What your computer does while you wait - 古斯塔夫·杜阿尔特

还有big-O notation的指南。


如果没有能够看到代码/伪代码,就无法回答这个问题。唯一可靠的答案是“使用分析器”。假设你的循环正在做什么对你和任何读这个问题的人都是一种伤害。

答案 2 :(得分:3)

我在这方面是否正确理解你?

你有这些循环:

for (...){
  // Do A
}

for (...){
  // Do B
}

for (...){
  // Do C
}

你把它转换成了

for (...){
  // Do A
  // Do B
}
for (...){
  // Do C
}

你想知道哪个更快?

如果没有,一些伪代码会很好,所以我们可以看到你的意思。 :) 不可能说。它可以采取任何一种方式。你是对的,获取数据很昂贵,但地点也很重要。第一个版本可能更适合数据局部性,但另一方面,第二个版本具有更大的块,没有分支,允许更有效的指令调度。

如果额外的表现真的很重要(正如Jon Skeet所说,它可能没有,你应该选择最可读的东西),你真的需要测量两个选项,看看哪个是最快的。

我的直觉是第二种,在跳转指令之间做更多工作会更有效率,但这只是一种预感,而且很容易出错。

答案 3 :(得分:3)

嗯,如果两个载体的大小不同,你就会出现并发症。正如已经指出的那样,这并没有增加问题的整体复杂性,所以我坚持使用最简单的代码 - 这可能是2个循环,而不是1个循环,复杂的测试条件是两个不同的长度。 / p>

实际上,这些长度测试可以轻松地使两个循环比单个循环更快。你也可以通过2个循环获得更好的内存提取性能 - 即你正在查看连续的内存 - 即A [0],A [1],A [2] ... B [0],B [1],B [ 2] ......,而不是A [0],B [0],A [1],B [1],A [2],B [2] ......

所以在各方面,我都会选择2个独立的循环;-p

答案 4 :(得分:1)

...处理一个数据但有两个函数有时可以使得处理该数据的代码不适合处理器的低级缓存。

for(i=0, i<10, i++ ) {
  myObject object = array[i];
  myObject.functionreallybig1(); // pushes functionreallybig2 out of cache
  myObject.functionreallybig2(); // pushes functionreallybig1 out of cache
}

VS

for(i=0, i<10, i++ ) {
  myObject object = array[i];
  myObject.functionreallybig1(); // this stays in the cache next time through loop
}


for(i=0, i<10, i++ ) {
  myObject object = array[i];
  myObject.functionreallybig2(); // this stays in the cache next time through loop
}

但这可能是一个错误(通常这种技巧被评论)

当像这样循环加载和卸载数据时,它被称为缓存抖动,顺便说一句。

这是这些函数正在处理的数据的单独问题,因为通常处理器会单独缓存它。

答案 5 :(得分:1)

除了缓存在大型函数上的颠簸之外,微小函数也可能有好处。这适用于任何自动矢量化编译器(不确定Java JIT是否会执行此操作,但您最终可以依赖它)。

假设这是您的代码:

// if this compiles down to a raw memory copy with a bitmask...
Date morningOf(Date d) { return Date(d.year, d.month, d.day, 0, 0, 0); }

Date timestamps[N];
Date mornings[N];

// ... then this can be parallelized using SSE or other SIMD instructions
for (int i = 0; i != N; ++i)
    mornings[i] = morningOf(timestamps[i]);

// ... and this will just run like normal
for (int i = 0; i != N; ++i)
    doOtherCrap(mornings[i]);

对于大型数据集,将可矢量化代码拆分为单独的循环可能是一个巨大的胜利(前提是缓存不会成为问题)。如果它全部保留为单个循环,则不会发生矢量化。

这是英特尔在其C / C ++优化手册中推荐的内容,它确实可以带来很大的不同。

答案 6 :(得分:1)

我很抱歉没有及早回复并提供任何类型的代码。我对我的项目产生了偏见,不得不处理其他事情。

回答任何仍在监控此问题的人;

是的,就像jalf所说,功能类似于:

    PrepareData(vectorA, VectorB, xArray, yArray):
        listA
        listB
        foreach(value in vectorA)
            convert values insert in listA
        foreach(value in vectorB)
            convert values insert in listB
        listC
        listD
        for(int i = 0; i < listB.count; i++)
            listC[i] = listB[i] converted to something
            listD[i] = listA[i]
        xArray = listC.ToArray()
        yArray = listD.ToArray()

我把它改为:

    PrepareData(vectorA, vectorB, ref xArray, ref yArray):
       listA
       listB
       for(int i = 0; i < vectorA.count && vectorB.count; i++)
            convert values insert in listA
            convert values insert in listB
        listC
        listD
        for(int i = 0; i < listB.count; i++)
            listC[i] = listB[i] converted to something
            listD[i] = listA[i]
        xArray = listC.ToArray()
        yArray = listD.ToArray()

请记住,向量可能包含大量项目。我认为第二个会更好,所以程序不必循环n次2或3次不同。但后来我开始怀疑内存提取或预取的影响(效果?),或者你有什么。

所以,我希望这有助于澄清这个问题,尽管你们中有很多人提供了很好的答案。

答案 7 :(得分:0)

谢谢大家的信息。从Big-O的角度思考以及如何优化从来都不是我的强项。我相信我会把代码放回原来的样子,我应该相信它之前写的方式,而不是跳过我的新手本能。此外,在未来我会提供更多参考,以便每个人都能理解我所说的内容(清晰度也不是我的强项: - /)。

再次感谢你。