3

我正在编写一些当前应该尽可能快地运行的 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 中的值将永远改变不能成对匹配的双精度数。

4

11 回答 11

11

你应该删除这个:

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,您可能获得更快的速度(尽管您随后需要担心管理多个线程的低级细节)。

于 2012-06-26T20:34:52.740 回答
7

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

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

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

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

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

于 2012-06-26T19:02:29.143 回答
6

尝试:

for (int i = 0; i < length; i++)
{
    var a = CAArray[i];
    Value += a.ClassB.Value * a.Multiplier;
}  
于 2012-06-26T18:54:52.207 回答
3

另一个可能影响非常大集合的性能的优化是定义 a ,而不是.fieldproperty

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

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

希望这可以帮助。

于 2012-06-26T18:58:18.267 回答
1

如果你有很多重复 wrt multiplier 和ClassB.Values 你可能想找到所有不同的对,将每对相乘一次,然后乘以这对的出现次数。

另外,我会去AsParallel()使用所有的核心。

于 2012-06-26T18:56:46.460 回答
1

我不知道您有多少控制权,ClassA但在我看来,既然MultiplierClassB是您的属性,ClassA您应该修改ClassA为具有此计算值的属性。从理论上讲,您已经将所有这些类都实例化了,并且已经设置了它们各自的属性,因此您可以轻松地计算或this.ClassB.Value * this.Multiplier的设置所需的值。通过这种方式,您可以降低此循环的成本,而是将其移向数据的实例化。这是一个值得的权衡吗?您需要更多地了解应用程序中发生的情况才能做出决定,但这会减少此特定功能的工作量。之后,您需要做的就是:ClassB.ValueMultiplier

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;
}

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

于 2012-06-26T19:41:39.647 回答
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 中的一个奇怪的错误,如果它被 try catch 包围,会使 for 循环运行得更快,因为 Jitter 在这种情况下更喜欢使用寄存器而不是 CPU 堆栈来存储本地变量。

于 2012-06-28T08:06:47.183 回答
0

还有一件事要小心 - 如果您经常分配非常大的数组(86K+ 数据)并且每次您可能会过度强调 GC,因为这些对象是在 LOH 上分配的,因此大小是不同的。

于 2012-06-26T19:16:02.893 回答
0

另一个轻微的改进是对索引使用 preincrement,因为 postincrement 必须返回迭代器在递增之前的值;因此,在使用适当的增量更改它之前,需要将先前的值复制到某个地方,因此它可以返回。

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

于 2012-06-26T19:06:55.680 回答
0
  1. 并行化它。
  2. 尝试展开循环。(编译器可能会自行执行此操作。)
于 2012-06-26T19:14:08.563 回答
-3

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

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

public void UpdateStuff()
{
    ClassA[] CAArray = new ClassA[large_number];
    Double Value = CAArray.Select(x => x.ClassB.Value * x.Multiplier).Sum();
}
于 2012-06-26T19:00:58.313 回答