基于范围的矢量化

g++ , range based for and vectorization

本文关键字:矢量化 范围 于范围      更新时间:2023-10-16

考虑以下c++ 11中基于for循环的范围

for ( T k : j )
{
  ...
}

g++clang++优化标志,可以加快编译代码?

我不是在谈论任何for循环,我只是在考虑这个新的c++ 11结构。

优化循环很少是关于优化实际的循环迭代代码(在本例中是for ( T k : j )),而是关于优化循环中的内容。

现在,因为在这种情况下这是...,所以不可能说,例如,展开循环是否有帮助,或者声明函数内联[或简单地移动它们以便编译器可以看到它们并将它们内联],使用自动向量化,或者在循环中使用完全不同的算法。

上面段落中的例子更详细一点:

  1. 展开循环-本质上是做几个循环迭代而不回到循环的开始。当循环内容非常小时,这是最有用的。有一种是自动展开,由编译器执行展开,或者您可以手动展开代码,方法是在每次循环迭代中执行四项,然后在每次循环变量更新中向前执行四项,或者在循环过程中多次更新迭代器[但这当然意味着不使用基于范围的for循环]。
  2. 内联函数——编译器将(通常是小的)函数放入循环中,而不是调用它们。这节省了处理器调用代码中的另一个位置并返回所花费的时间。大多数编译器只对编译过程中对编译器"可见"的函数执行此操作-因此源文件必须位于相同的源文件中,或者位于被编译的源文件中包含的头文件中。
  3. 自动向量化-使用SSE, MMX或AVX指令在一条指令中处理多个数据项(例如,一条SSE指令可以在一条指令中将四个float值添加到另外四个float中)。这比一次操作单个数据项要快(大多数情况下,有时没有好处,因为尝试组合不同的数据项,然后在计算完成后对哪些数据进行排序会带来额外的复杂性)。
  4. 选择不同的算法——通常有几种方法来解决特定的问题。根据您想要实现的目标,for循环(任何类型的)可能不是正确的解决方案,或者循环内的代码可能会使用更聪明的方法来计算/重新排列/无论它做什么,以实现您需要的结果。

但是...太模糊了,无法说明上述解决方案中的哪一个(如果有的话)将有助于改进您的代码。

关于自动向量化的GCC文档没有提到任何关于基于范围的for循环的内容。同样,它的代码可以归结为:

{
    auto && __range = range_expression ;
    for (auto __begin = begin_expr,
                __end = end_expr;
            __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

因此,从技术上讲,任何有助于自动向量化这种常规for中的结构的标志都应该自动向量化类似的基于范围的for循环。我真的这样做了,编译器只将基于范围的for循环转换为常规的for循环,然后让自动向量化在这些旧循环上完成它的工作。这意味着不需要一个标志来告诉你的编译器在任何情况下自动向量化你的基于范围的for循环。


由于GCC的实现被要求,这里是源代码中的相关注释,描述了基于范围的for循环的实际操作(如果您想查看代码,可以检查实现文件parser.c):

/* Converts a range-based for-statement into a normal
   for-statement, as per the definition.
      for (RANGE_DECL : RANGE_EXPR)
    BLOCK
   should be equivalent to:
      {
    auto &&__range = RANGE_EXPR;
    for (auto __begin = BEGIN_EXPR, end = END_EXPR;
          __begin != __end;
          ++__begin)
      {
          RANGE_DECL = *__begin;
          BLOCK
      }
      }
   If RANGE_EXPR is an array:
    BEGIN_EXPR = __range
    END_EXPR = __range + ARRAY_SIZE(__range)
   Else if RANGE_EXPR has a member 'begin' or 'end':
    BEGIN_EXPR = __range.begin()
    END_EXPR = __range.end()
   Else:
    BEGIN_EXPR = begin(__range)
    END_EXPR = end(__range);
   If __range has a member 'begin' but not 'end', or vice versa, we must
   still use the second alternative (it will surely fail, however).
   When calling begin()/end() in the third alternative we must use
   argument dependent lookup, but always considering 'std' as an associated
   namespace.  */

如您所见,它们所做的不过是标准实际描述的内容。