for循环的优化

时间:2012-06-26 18:51:09

标签: c# .net c#-4.0 optimization

大家好我正在编写一些目前应该尽可能快地运行的c#代码,通常占用100%的单核心大约25分钟。我需要代码保持单核心,因为跨多个核心运行此代码的好处不会像同时多次运行此项目一样好

有问题的代码如下

public Double UpdateStuff(){

    ClassA[] CAArray = ClassA[*a very large number indeed*];
    Double Value = 0;
    int length = CAArray.Length;

    for (int i= 0; i< length ; i++)
        {
         Value += CAArray[i].ClassB.Value * CAArray[i].Multiplier;
        }  
    return Value;
}

根据分析器,代码的这个区域负责应用程序负载的78%,因此似乎是优化的良好候选者。

!!!注意:该函数已从返回类型void更改为返回类型Double,这是伪代码而不是实际代码以便于阅读

澄清:.net,c#4.0,visual studio 2010,目标机器:windows server 2008 x64

编辑:进一步澄清:此上下文中的所有变量都是公共的而不是属性。 CAArray [i] .ClassB.Value中的值将永远改变无法配对的双打。

11 个答案:

答案 0 :(得分:10)

你应该删除它:

int length = CAArray.Length;

并用以下代码替换循环:

for (int i= 0; i < CAArray.Length; i++)
{
    Value += CAArray[i].ClassB.Value * CAArray[i].Multiplier;
} 

像原始代码一样存储长度实际上减慢了 C#代码(反直觉,我知道)。这是因为如果您在for循环中直接使用Array.Length,则抖动将跳过对循环的每次迭代执行数组边界检查。

此外,我强烈建议并行化此过程。最简单的方法是

CAArray.AsParallel().Sum(i => i.ClassB.Value * i.Multiplier);

虽然你可以潜在地在没有LINQ的情况下获得更快的速度(尽管你需要担心管理多个线程的低级细节)。

答案 1 :(得分:6)

尝试:

for (int i = 0; i < length; i++)
{
    var a = CAArray[i];
    Value += a.ClassB.Value * a.Multiplier;
}  

答案 2 :(得分:6)

一个区别是在for循环中使用临时变量来保存当前值。

第二个区别,可能更重要的是,在for-loop边界中放置CAArray.Length而不是count。编译器优化这样的循环以消除边界检查。

for (int i = 0; i < CAArray.Length; i++)
{
    var curr = CAArray[i];
    Value += curr.ClassB.Value * curr.Multiplier;
}

如果可以的话,你可以做的另一件事是将ClassB,ClassB.Value和Multiplier属性作为字段。

最后 - 记得检查解决方案属性中的“优化代码”,让编译器优化代码。

答案 3 :(得分:3)

另一个优化,可以通过对非常大的集合上的性能的影响,定义field,而不是property

for (int i= 0; i< length ; i++)
{
    var a = CAArray[i];
    Value += a.ClassB.value_field * a.multiplier_field;
}  

即使MS建议使用属性,众所周知,属性引入的开销非常小(但可能与非常大的数据相关)。

希望这有帮助。

答案 4 :(得分:1)

如果你有很多重复的乘数和ClassB.Value s,你可能想要找到所有不同的对,将每一对乘以一次,然后乘以该对的出现次数。

另外,我会选择AsParallel()并使用所有核心。

答案 5 :(得分:1)

我不知道您对ClassA有多少控制权,但在我看来,由于MultiplierClassBClassA的属性,您应该修改ClassA 1}}具有此计算值的属性。从理论上讲,您已经将所有这些类实例化,并且已经设置了各自的属性,因此您可以在this.ClassB.Value * this.MultiplierClassB.Value的设置下轻松计算Multiplier的所需值。通过这种方式,您可以降低此循环的成本,而不是将其移至实例化数据。这是值得的权衡吗?您需要了解有关应用程序中发生的事情的更多信息,但这将减少此特定功能的工作量。之后您需要做的就是:

public void UpdateStuff(){

    ClassA[] CAArray = ClassA[*a very large number indeed*];
    Double Value = 0;
    int length = CAArray.Length;

    for (int i= 0; i< length ; i++)
    {
        Value += CAArray[i].MultipliedClassBValue;
    }
return Value;
}

加上这里的优秀人才可以提出的进一步改进。

答案 6 :(得分:0)

另一个小改进是使用preincrement作为索引,因为postincrement必须返回迭代器在递增之前的值;所以,之前的值需要在使用适当的增量进行更改之前复制到某处,因此可以返回。

额外的工作可能有点或很多,但它肯定不能小于零,与preincrement相比,它可以简单地执行递增,然后返回刚改变的值 - 不复制//保存//等必要的。

答案 7 :(得分:0)

  1. 并行化。
  2. 尝试展开循环。 (编译器可以单独执行此操作。)

答案 8 :(得分:0)

还有一件事需要注意 - 如果你经常分配非常大的数组(86K +数据),并且每次因为这个对象在LOH上分配而过多地强调GC时,大小就不同了。

答案 9 :(得分:0)

由于数组中包含大量元素,因此这种方法比其他循环迭代方法更快。

try
{
    for (int i= 0; ; i++)
    {
        var a = CAArray[i];
        Value += a.ClassB.value_field * a.multiplier_field;
    }
}
catch (IndexOutOfRangeException)
{ }

虽然不可否认它看起来相当丑陋,绝对不是一种“纯粹的”编程方式。 但同时使用公共字段而不是属性并不纯粹。

除了从删除退出条件中获得的收益之外,CLR 2.0 for X86中的一个奇怪的错误使得for循环运行得更快如果它被try catch作为Jitter包围在这种情况下某种方式更喜欢使用寄存器而不是CPU堆栈来存储本地人。

答案 10 :(得分:-3)

首先,它是一个空白,所以它不应该返回任何东西(或者它应该返回一个Double)。其次,C#一般不使用埃及大括号 - 但这并不重要。

然后你可以尝试使用Linq和lambdas,我认为它可能更快 - 至少更清洁!

public void UpdateStuff()
{
    ClassA[] CAArray = new ClassA[large_number];
    Double Value = CAArray.Select(x => x.ClassB.Value * x.Multiplier).Sum();
}