【问题标题】:Fastest way to iterate conditionally backward or forward [closed]有条件地向后或向前迭代的最快方法[关闭]
【发布时间】:2017-07-09 15:23:50
【问题描述】:

我能想到至少三种选择循环方向的方法。

两个循环,一个开始的条件(也许是最快的?):

if (!backwards)
  for (int i = 0; i <= end; i++) {
  // code
  }
else
  for (int i = end; i >= 0; i--){
  // code
  }

循环一些元素,在里面测试和递增(我用这个):

for (int l = 0; l < max_len; l++) {
  // code
  if (!backward)
    i++;
  else
    i--;
}

使用可变增量和结束值(可能是最差的?)

if (backward)
  inc = -1;
else
  inc = 1;
for (int i = 0; i != end; i += inc) {
  // code
}

哪种方式更快?编译器是否在每种情况下都对其进行优化?

【问题讨论】:

  • 第二个解决方案可能是最糟糕的(顺便说一句,错误的,我让你找出原因)。是什么让您认为第三种解决方案最糟糕?
  • 第三种解决方案也是错误的。起始值有点偏离。
  • 糟糕,谢谢!是的,对于最后一个,结束应结束+1或结束-1,具体取决于增量。对于第一个,我不明白为什么它看起来应该关闭?
  • 不可能在这个级别回答“最快”类型的问题,因为答案可能会因许多因素(如 CPU 规格、优化、缓存等)而有所不同。跨度>
  • 我的第一个想法是第一个是最快的

标签: c loops optimization


【解决方案1】:

在没有考虑特定系统的情况下讨论性能并不是很有意义。对于“通用计算机”,这里要考虑的事情是

  • 生成的实际机器代码。更少的 CPU 滴答声可让任何 CPU 上的程序更快。
  • 分支数。更少的分支意味着更好的分支预测可能性,并且 CPU 可以利用指令缓存(如果存在)。
  • 循环完成的实际工作。这可能是最重要的部分。假设循环对数组做了一些事情。如果数组是按顺序访问的,从数据的顶部到数据的底部,这意味着CPU可以利用数据缓存。

一种改进机器代码的旧方法是尽可能编写递减计数循环,因为这会导致“如果为零则分支”指令,这比“如果等于则分支”要快一些。然而,这种技术起源于编译器垃圾的黑暗时代。对于现代的优化编译器,迭代顺序不应该是性能问题。所以这个技巧基本上已经过时了。

除此之外,不同的循环可能会产生比彼此稍微更高/更低效率的代码,具体取决于系统。您可以拆卸不同的版本并检查,但这是一个非常小的问题。

关于分支,第 3 个版本显然比其他版本好得多,因为它只包含一个分支 - 对循环迭代器的检查,它给出了循环本身。第一个版本更差,第二个版本更差。

根据循环的实际作用,第三版可能不适合数据缓存。没法说。

总的来说,这两个版本之一可能是最有效的:

for(size_t i=start; i!=end; i+=inc)

或许

size_t offset = backwards ? n-1 : 0;
for(size_t i=0; i<n; i++)
{
  size_t index = i - offset;
  arr[index] = something;
}

但唯一的判断方法是实际进行基准测试和反汇编。为此,您需要指定一个特定的系统。

【讨论】:

  • 向下计数可以更快的原因是序列是递减,如果不为零则循环,而向上计数是递增,比较,如果不相等则循环。
  • @rcgldr 这不是程序员应该关心的事情,除非他们正在编写汇编程序。在现代编程中,编写这样的向下计数循环是“过早优化”的完美示例。
  • 我的评论是为了解释在某些情况下性能确实很重要的“旧”系统减少计数的“旧”原因。这不适用于正常的“现代”系统。
【解决方案2】:

根据您选择的选项,我会避免使用方法 2,因为它会在关键循环中可能会避免的每个元素上添加检查/分支。如果您先验地知道您想要的元素元素靠近数组的后面还是前面,那么方法 1 或 3 可能是最好的。

比较 1 和 3 不太直接。我相信在 Intel X86 处理器上,性能将与示例 1 中的 for 循环相同。即 ++i 和 i += 1 都将转换为添加指令,并且 NE(不等于)比较将等同于 LE(小于等于)。但是,一般来说,要确定您需要检查您正在使用的处理器/编译器的反汇编。

注意:此线程中还提到倒计时循环(与 0 相比)可能会在某些处理器上提供轻微的速度优势。此外,如果您使用前增量 ++i 而不是示例中的后增量,某些处理器将产生轻微的性能提升。

顺便说一句:如果您想检查数组结构中的所有元素,并且如果您的 c 库支持并行性,Parallel For 可能被证明是最快的,您将元素数除以处理器线程数.

使用具有 4 个处理器线程的 Parallel For 示例:如果您的数组包含 100 万个元素并且您有 4 个线程,您可以让线程 1 迭代 0 到 249.999,线程 2 250,000 到 499,999,线程 3 500,000 到 749,999 , 和线程 4 750,000 到 999,999 以同时的方式。总的理论增益将是 4 倍 - 减去一些开销加上等待最慢线程完成的时间。 (在这种情况下,这个时间量应该是最少的)。

【讨论】:

  • 这被标记为 C。请不要发布其他编程语言的答案。
  • 谢谢,我编辑了答案以使其符合 C 标准。
猜你喜欢
  • 1970-01-01
  • 2012-11-14
  • 2012-01-31
  • 2010-09-12
  • 2013-01-19
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 2019-02-15
相关资源
最近更新 更多