在循环中使用Else语句会导致性能下降吗?
本文关键字:性能 循环 语句 Else | 更新日期: 2023-09-27 18:14:47
我正在用c#写一个蒙特卡罗模拟程序,我正在努力确保我写的代码尽可能高效——我正在运行数十亿个循环,事情变得越来越慢。我有一个关于在循环内使用Else
语句的问题。
我的问题是:这两种方法在性能上有什么不同吗?在第一个语句中,我使用If-Else语句,而在第二个语句中,我省略了Else语句,因为If情况非常罕见。
编辑:让我们假设我需要做的不仅仅是在条件满足时分配true/false,这样直接分配就不是唯一需要做的事情。if-Else方法的执行速度一样快吗?
//METHOD 1
...
for (int index = 0; index < 6; index++)
{
for (int x = 0; x < 50; x++)
{
for (int y = 0; y < 50; y++)
{
bool ThingWhichIsVeryRarelyTrue = SomeFunction(index,x,y);
if (ThingWhichIsVeryRarelyTrue)
{
BooleanAnswerArray[index][x][y] = true;
DoSomeOtherStuff();
}
else
{
BooleanAnswerArray[index][x][y] = false;
}
}
}
}
...
//METHOD 2
for (int index = 0; index < 6; index++)
{
for (int x = 0; x < 50; x++)
{
for (int y = 0; y < 50; y++)
{
BooleanAnswerArray[index][x][y] = false;
bool ThingWhichIsVeryRarelyTrue = SomeFunction(index,x,y);
if (ThingWhichIsVeryRarelyTrue)
{
BooleanAnswerArray[index][x][y] = true;
DoSomeOtherStuff();
}
}
}
}
...
在您的示例中,直接赋值应该是完美的:
BooleanAnswerArray[index][x][y] = SomeFunction(index,x,y);
旁注:尝试缓存数组访问可能是一个好主意-应该能够缓存var row = BooleanAnswerArray[index][x]
,这样你就可以避免在最内层循环中额外的索引。
使用if/else更新问题:
首先,这是一个性能问题,所以必须衡量不同的选项,看看代码是否满足目标。Stopwatch
类通常足以进行这种局部性能比较,否则可能需要profiler。
猜测
-
if
将导致完全相同的影响,无论它是否有一个或两个分支,特别是在存在任何其他不重要的代码,如非内联函数调用。 - 循环展开可能会产生更大的影响(也会使代码可读性降低)
- 尽可能多的缓存可能会产生更大的影响
在第一个语句中我使用了If-Else语句,在第二个语句中我省略了Else,因为If情况非常少见。
这仅仅意味着您的第二个程序不会完全像第一个程序一样执行(除非您可以证明永远不需要else
…)。所以这不是性能问题,你的第二个程序是错误的(假设你的第一个程序是正确的,并且需要else)。
不输出正确结果的快速程序是一件坏事。
在这种特殊情况下,一个好的编译器会将代码优化为类似alzaimar的答案。不过,为了便于阅读,您也应该这样写。在一般情况下,else可能(将)包含通过称为branch prediction failure
的东西进行的性能损失。现代CPU会"猜测"程序流是经过if
还是else
,然后执行该分支。如果它后来发现它做了错误的猜测,它必须返回并通过正确的分支。
如果这确实成为一个问题,您应该确保使用if
和else
分支的顺序遵循一个简单的模式。
请参阅这个问题及其答案了解更多细节。
性能下降的第二个原因是没有利用数据局部性或引用局部性。这意味着你应该使用紧密相连的数据(例如myArray[100]和myArray[101])。
在特殊情况下,不要改变数组的索引顺序。
先写一个正确的程序。然后优化它的痛处。也就是说,分析器显示你的程序花费了太多的时间。优化不重要的东西是没有用的
是什么阻碍了您将代码简化如下:
for (int index = 0; index < 6; index++)
for (int x = 0; x < 50; x++)
for (int y = 0; y < 50; y++)
BooleanAnswerArray[index][x][y] = SomeFunction(index,x,y);
很抱歉踢括号。我不太喜欢他们;-)
第一个应该稍微快一点。但如果我是你,我会更关心代码的可读性,而不是代码的良好性能(直到你开始做数百万次的事情)……只是我的2c
方法1只计算一次3级索引,而不是两次。编译器能否对其进行优化还不确定。但是,当然,一切都取决于SomeFunction
使用的相对时间。