【问题标题】:Is there a more efficient method for omitting for loop instances是否有更有效的方法来省略循环实例
【发布时间】:2012-03-31 04:37:22
【问题描述】:

如果我有一个标准的 for 循环,是否有更有效的方法来省略某些事件?

例如:

答:

        for (int i = 0; i < n; i++)
        {
            if (i != n / 2 && i != n / 3 && i != n / 4)
            {
                val += DoWork(i);
            }
            else
            {
                continue;
            }
        }

乙:

        for (int i = 0; i < n / 4; i++)
        {
            val += DoWork(i); ;
        }
        for (int i = n / 4 + 1; i < n / 3; i++)
        {
            val += DoWork(i);
        }
        for (int i = n / 3 + 1; i < n / 2; i++)
        {
            val += DoWork(i);
        }
        for (int i = n / 2 + 1; i < n; i++)
        {
            val += DoWork(i);
        }

C:

        for (int i = 0; i < n; i++)
        {
            if (i == n / 2 || i == n / 3 || i == n / 4)
            {
                continue;
            }
            else
            {
                val += DoWork(i);
            }
        }

对于n = int.MaxValue,结果如下: A 结果:57498 毫秒。 B 结果:42204 毫秒。 C 结果:57643 毫秒。

根据@Churk 的回答进行编辑。 我添加了另一个测试用例方法D如下:

D:

        for (int i = 0; i < n; i =  (i != n / 2 -1 && i != n / 3 -1  && i != n / 4-1) ? i+1: i+2)
        {
            val += DoWork(i);
        }

得到以下结果:

A 结果:56355 毫秒。 B 结果:40612 毫秒。 C 结果:56214 毫秒。 D 结果:51810 毫秒。

Edit2:在循环外预先计算 n/2 n/3 和 n/4 的值后:

A 结果:50873 毫秒。 B 结果:39514 毫秒。 C 结果:51167 毫秒。 D 结果:42808 毫秒。

D 循环似乎再次接近 B 的“展开”,而 A 和 C 仍需要更多时间。

就每种方法之间的比较而言,这与我的预期相符。

我的问题是,有没有更有效的方法来做到这一点?

【问题讨论】:

  • 这是您应用程序的性能瓶颈吗?听起来不太可能。
  • 你不需要else{continue;}。在C: 示例中,将if 语句更改为if(!(..)){ val += DoWork(i); } 并完全省略else
  • 如果你做了 DoWork(0);DoWork(1);DoWork(2)...DoWork(n) 它可能会更快。
  • 别在意我的回答,只是意识到它会在 for 循环遇到这些条件之一而不继续时停止。
  • 是的。如果它不是硬编码的,那么值得预先计算这些表达式,例如:n/2、n/3 并为它们分配变量,而不是在循环中每次都计算它们。

标签: c# performance


【解决方案1】:

这在一定程度上取决于上下文。在许多情况下,一种可能性是作弊。因此,不要省略数字,只需包含它们,然后将结果与您不想要的数字相反:

    for (int i = 0; i < n; i++) 
    { 
        val += DoWork(i); 
    } 
    val -= DoWork(i/2);
    val -= DoWork(i/3);
    val -= DoWork(i/4);

您从比较中节省的时间可能会超过计算某些数字两次的结果,具体取决于 DoWork 操作的成本。

【讨论】:

  • +1 的想法,尽管我觉得这在大多数情况下并不能优化性能。
  • 只有在少数异常情况下才干净的实现。如果 DoWork 依赖于某些条件,例如某个时间点的运行总计,会发生什么?我不确定除了操作员发布的内容之外是否还有更优雅的方式。
  • 由于该问题没有提供足够的上下文,因此它是一个理论练习。该代码没有提到 DoWork 的任何副作用。但是,当然,如果有的话,这可能行不通。在真正考虑循环性能的情况下,它应该优化性能。如果 DoWork 做了很多工作,那么您将尝试优化 DoWork 而不是循环。所以我假设 n 相当大。
  • 这真的取决于 DoWork() 需要多少时间。只有当 DoWork() 相对于循环而言不是昂贵的操作时,它才会正常工作。
【解决方案2】:

将您的次要条件嵌入到您的 for 循环中

未经测试:

for (int i = 0; i < n || (i != n / 2 && i != n / 3 && i != n / 4); i++)
  val += DoWork(i);
}

我认为只要其中一个条件为真,它就会继续运行

【讨论】:

  • 它有点老套,而且不那么冗长。
  • 我第二次猜测自己,并立即为其设置测试用例。但我认为 i
  • 是的,我相信 Churk,我也尝试过这个,但无法让它正常工作,如果你让它 i &lt; n &amp;&amp; (i != n / 2 &amp;&amp; i != n / 3 &amp;&amp; i != n / 4) 对我来说似乎更有可能,但它在之后进入无限循环i >= n
  • 我认为这行不通,因为在循环的每次迭代之后都会检查条件,而且它会给你不正确的结果,因为你包含了你应该排除的事件(i)
  • @surfen 他并不完全正确,但它让我在编辑我的问题时进入循环,它实际上比其他每个都快得多(除了 B 可能最终会是最快)
【解决方案3】:

首先,在这 40-60 秒内暂停几次,这样您就可以大致了解DoWork 中的时间比例。如果你甚至可以让你的循环完全不花时间,你仍然需要花费那部分。

现在看看你的比较。 每个人都在问一个问题。 如果一个问题的答案几乎总是 True 或几乎总是 False,那么这是一个加快速度的机会。 (所有的 log(n) 和 n*log(n) 算法都通过使它们的决策点更像公平的硬币来工作。)

所以你可以看到为什么你的 B 更快。平均而言,它在每个循环中询问的问题更少。 您可以通过展开循环(更少地提问)来加快速度。

(我知道,我知道,编译器可以展开循环。嗯,也许。你自己做,你就不用赌了。)

【讨论】:

  • @NominSim:只有在以下情况下才值得担心:1)整个事情占用了总时间的很大一部分,并且 2)DoWork 几乎什么都不做。我只提到这一点是因为有时人们“精打细算”。
  • 嗯,从 A 和 C 到 B 甚至 D 的改进对我来说似乎相当重要(快了 130%)
  • @NominSim:你知道我的意思。如果这是在一个更大的应用程序中,它在这段代码中花费的时间可能很小,DoWork可能更像DoesMoreWorkThanIThoughtItWould
  • 是的,确实如此,但如果在我的一生中,我通过为每个循环使用最佳 for 循环来观察性能的轻微提升,那会让我晚上睡得更好。
【解决方案4】:

您可以在循环之外计算值,然后跳过它们:

  int skip1 = n/2;
  int skip2 = n/3;
  int skip3 = n/4;
  for (int i = 0; i < n; i++)
  {
      if (i != skip1 && i != skip2 && i != skip3)
      {
         val += DoWork(i);
      }
  }

不过,不确定这是否会节省足够的毫秒时间。

更新:我刚刚注意到 @surfen 在 cmets 中提出了这个建议。

【讨论】:

    【解决方案5】:

    如果DoWork() 不是瓶颈,它是一个足够小的方法嵌入到循环中,因此您不需要调用本身会花费时间的调用。如果DoWork() 完成了大部分工作,那你就是在浪费时间:)

    【讨论】:

      【解决方案6】:

      您可以计算操作...

      选项 A:
      n 在 for 循环中检查 i,然后对每个不是该值的 i 进行 3 次检查...所以只有 4n 次操作用于检查。

      选项 B:
      你只是在循环间隔,所以你在做n-3 操作。

      选项 C:
      与选项 A 相同,4n 操作。

      【讨论】:

        【解决方案7】:

        我不会费心用这样的修改使代码复杂化(因为很多人已经对你的问题发表了评论)

        相反,您可能希望使用Pararell.ForEach 在多个线程中同时运行您的 DoWork。如果您的 DoWork() 执行任何操作,这将对性能产生更大的影响。

        【讨论】:

        • 有时“DoWork”函数不能并行完成,我只是想看看是否有针对这种情况的更好的优化。
        • 那我猜答案是NO(B是最快的)。由于可读性,我仍然会选择 A。
        • 对于 B,如果您在循环之前定义 i 变量,您可以获得更多毫秒 - 您不必在每个循环之前计算它,只需从完成的地方继续。
        猜你喜欢
        • 2021-02-28
        • 2017-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多