【问题标题】:Is there any difference between these two for loops?这两个for循环有什么区别吗?
【发布时间】:2019-09-19 18:11:49
【问题描述】:

从功能上讲,这两个循环之间没有区别,但是选择一个循环有什么好处吗?

for (var i = 0; i < loopLimit; i++)
{

}

VS

for (var i = 0; loopLimit > i; i++)
{

}

将较大的数字放在&lt; 符号的左侧与右侧有什么区别?


运行非常基本的测试我注意到将较大的数字放在&lt; 的左侧似乎总是比放在左侧时稍好一些。以下是一小部分结果:

loopLimit &gt; i 平均 41,479,219 滴答声,而 i &lt; loopLimit 平均 41,610,158 滴答声以完成以下测试循环:

void MeasureLoop()
{
    const int loopLimit = Int32.MaxValue;

    var timer = new Stopwatch();    
    timer.Start();

    for (var i = 0; i < loopLimit; i++)
    {
        //
    }

    timer.Stop();
}

void MeasureLoop()
{
    const int loopLimit = Int32.MaxValue;

    var timer = new Stopwatch();    
    timer.Start();

    for (var i = 0; loopLimit > i; i++)
    {
        //
    }

    timer.Stop();
}

【问题讨论】:

  • 功能上,没有。但是,第一个在 C#、Java 和 C/C++ 中是惯用的。看到第二个的人可能会对为什么这样做感到困惑,并且可能会因此寻找不同的行为,即使没有。
  • 如果i &lt; xx &gt; i 快,我会很惊讶。
  • 我投票结束这个问题,因为它是关于理解事物编写方式的人性。这与语法或性能无关。
  • @JesseJohnson - 你专注于错误的领域。您的客户不在乎您生产的产品是否需要 350 毫秒而不是需要 340 毫秒的产品。注意减速的地方(可能不是这个循环),测量和补救。 不要尝试从他人那里学习/神化如何构建高性能软件;特别是如果他们不太可能以高性能为目标。

标签: c# .net performance


【解决方案1】:

这个问题被标记为 虽然从正文中不清楚这是严格一个性能问题还是对这两种风格之间的其他可能偏离主题的差异(例如可读性)持开放态度,通用约定等。无论如何,为了解决性能方面的问题,我使用BenchmarkDotNet 编写了在非迭代器操作数是常量以及它是变量的情况下比较两个运算符的基准测试。 .

using BenchmarkDotNet.Attributes;

namespace SO58016813
{
    public static class Program
    {
        public static void Main()
        {
            BenchmarkDotNet.Running.BenchmarkRunner.Run<Benchmarks>();
        }
    }

    [ClrJob()]
    [CoreJob()]
    [CategoriesColumn()]
    [GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
    public class Benchmarks
    {
        private const int LoopLimit = int.MaxValue;

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("Constant operand")]
        public void IteratorLessThanConstant()
        {
            for (var i = 0; i < LoopLimit; i++)
            {
                // Do nothing...
            }
        }

        [Benchmark()]
        [BenchmarkCategory("Constant operand")]
        public void ConstantGreaterThanIterator()
        {
            for (var i = 0; LoopLimit > i; i++)
            {
                // Do nothing...
            }
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("Variable operand")]
        public void IteratorLessThanVariable()
        {
            var loopLimit = LoopLimit;

            for (var i = 0; i < loopLimit; i++)
            {
                // Do nothing...
            }
        }

        [Benchmark()]
        [BenchmarkCategory("Variable operand")]
        public void VariableGreaterThanIterator()
        {
            var loopLimit = LoopLimit;

            for (var i = 0; loopLimit > i; i++)
            {
                // Do nothing...
            }
        }
    }
}

我事先做了一些研究,以确保不会优化空循环,并且根据Is there a way to get the .Net JIT or C# compiler to optimize away empty for-loops?,它们不会。这是我在 .NET Framework 和 .NET Core 上得到的结果...

// * Summary *

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i7 CPU 860 2.80GHz (Nehalem), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.1.802
  [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
  Clr    : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4010.0
  Core   : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT


|                      Method |  Job | Runtime |       Categories |    Mean |    Error |   StdDev | Ratio |
|---------------------------- |----- |-------- |----------------- |--------:|---------:|---------:|------:|
|    IteratorLessThanConstant |  Clr |     Clr | Constant operand | 1.285 s | 0.0063 s | 0.0059 s |  1.00 |
| ConstantGreaterThanIterator |  Clr |     Clr | Constant operand | 1.282 s | 0.0021 s | 0.0020 s |  1.00 |
|                             |      |         |                  |         |          |          |       |
|    IteratorLessThanVariable |  Clr |     Clr | Variable operand | 1.288 s | 0.0065 s | 0.0061 s |  1.00 |
| VariableGreaterThanIterator |  Clr |     Clr | Variable operand | 1.282 s | 0.0028 s | 0.0026 s |  1.00 |
|                             |      |         |                  |         |          |          |       |
|    IteratorLessThanConstant | Core |    Core | Constant operand | 1.286 s | 0.0082 s | 0.0077 s |  1.00 |
| ConstantGreaterThanIterator | Core |    Core | Constant operand | 1.287 s | 0.0072 s | 0.0067 s |  1.00 |
|                             |      |         |                  |         |          |          |       |
|    IteratorLessThanVariable | Core |    Core | Variable operand | 1.284 s | 0.0063 s | 0.0059 s |  1.00 |
| VariableGreaterThanIterator | Core |    Core | Variable operand | 1.286 s | 0.0075 s | 0.0071 s |  1.00 |

根据以上结果...

  • 如果您要问一个运算符是否比另一个更快,那么我会说不。
  • 如果您问应该使用哪个运算符,我会建议您认为哪个运算符可以为您的代码提供最大的可读性和清晰度,因为这比任何微优化(如果存在)都重要得多。

【讨论】:

  • 优秀。这很棒。谢谢!
【解决方案2】:

不,没有。但是你应该使用第一个,因为每个人都这样做。

【讨论】:

  • [需要引用]
  • 我知道看到它以第一种样式index &lt; number 编写的情况要普遍得多,但我想知道为什么这会成为常态。
  • 也许你应该问Kernighan or Ritchie
  • @JesseJohnson 可能是因为这完全是为了限制循环计数器,所以很自然地把它放在第一位。
  • To a@IanMercer 链接的好文章!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
  • 1970-01-01
  • 2013-11-14
  • 1970-01-01
  • 2020-10-14
  • 2014-07-07
  • 2019-11-15
相关资源
最近更新 更多