【问题标题】:Inlining of a recursive function递归函数的内联
【发布时间】:2019-09-03 05:42:16
【问题描述】:

当我尝试编译这段代码时:

#include <iostream>
#include <limits.h>

// End recursive template-expansion of function select below.
template <typename Type>
static inline constexpr Type select(unsigned index)
{ return Type(); }

// Select one of the items passed to it.
// e.g. select(0, a, b, c) = a; select(1, a, b, c) = b; etc.
template <typename Type, typename... Params>
[[gnu::always_inline]]
static inline constexpr Type select(unsigned index, Type value, Params... values)
{ return index == 0 ? value : select<Type>(index - 1, values...); }

template <typename Type>
[[gnu::always_inline]]
static inline constexpr Type reflect_mask_helper_1(Type mask, Type shift, Type value)
{ return ((value & mask) >> shift) | ((value << shift) & mask); }

template <typename Type>
[[gnu::always_inline]]
static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
{
  return i == 0
    ? value
    : reflect_mask_helper_0(
        i - 1,
        reflect_mask_helper_1<Type>(
          select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
                        0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
          1 << (i - 1),
          value));
}

template <typename Type>
[[gnu::flatten]]
static inline constexpr Type reflect_mask(Type value)
{ return reflect_mask_helper_0(__builtin_ctz(sizeof(Type) * CHAR_BIT), value); }

int main(void) {
  for (int i = 0; i < 65536; i++) {
    std::cout << reflect_mask<uint16_t>(i) << std::endl;
  }
}

gcc 给我一个错误,说函数 reflect_mask_helper_0 不能内联,因为它是递归的。但是,函数select 也是递归的,但 gcc 将其内联而不抱怨。我在这里错过了什么?

(我需要它是递归的,因为constexpr 函数在 C++11 下不能包含循环。)

错误信息:

% g++ test.cpp -O3 -march=native -c
test.cpp: In function ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’:
test.cpp:23:30: error: inlining failed in call to always_inline ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’: recursive inlining
   23 | static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
      |                              ^~~~~~~~~~~~~~~~~~~~~
test.cpp:27:28: note: called from here
   27 |     : reflect_mask_helper_0(
      |       ~~~~~~~~~~~~~~~~~~~~~^
   28 |         i - 1,
      |         ~~~~~~              
   29 |         reflect_mask_helper_1<Type>(
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |           select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                         0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   32 |           1 << (i - 1),
      |           ~~~~~~~~~~~~~     
   33 |           value));
      |           ~~~~~~~           
test.cpp: In function ‘int main()’:
test.cpp:23:30: error: inlining failed in call to always_inline ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’: recursive inlining
   23 | static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
      |                              ^~~~~~~~~~~~~~~~~~~~~
test.cpp:27:28: note: called from here
   27 |     : reflect_mask_helper_0(
      |       ~~~~~~~~~~~~~~~~~~~~~^
   28 |         i - 1,
      |         ~~~~~~              
   29 |         reflect_mask_helper_1<Type>(
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |           select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                         0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   32 |           1 << (i - 1),
      |           ~~~~~~~~~~~~~     
   33 |           value));
      |           ~~~~~~~

【问题讨论】:

  • 如果您将i 转换为泛型参数并为0 提供非递归特化,则应该可以内联
  • 不确定您使用的是哪个版本,我在g++ (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4 上尝试过,编译后没有任何错误/警告。
  • @sklott,我在 Arch Linux 中使用 gcc 9.1.0。
  • 您知道,答案部分属于解决方案。把它放在问题中只是令人困惑。我建议您将其发布为答案并自我接受。没有难过的感觉。

标签: c++ recursion compiler-optimization micro-optimization inlining


【解决方案1】:

select 实际上并不调用自身。它弹出它收到的类型列表的前面,然后调用select&lt;Type, ...&gt;另一个特化。尾随参数包是不同的。由于“递归”本质上是一组有限的嵌套函数调用(不同的函数),GCC 可以直接看穿它,而不管运行时参数如何。

reflect_mask_helper_0 会无限期地调用自身,并使用相同的模板参数。 GCC 无法判断这个运行时递归在运行时会走多远。回想一下,constexpr 函数仍然是一个必须在运行时调用的常规函数​​。

【讨论】:

  • 确实,它调用了自己,但是为什么它看不到reflect_mask_helper_0总是停止呢?事实上,查看当我删除 [[gnu::always_inline]] 时它生成的代码,它确实在 main() 中内联了对 reflect_mask_helper_0 的调用,但它也保留了未引用的非内联代码,即我尝试添加 [[gnu::always_inline]] 的原因。
  • @AndrévonKugland - 我怀疑这是由于contexpr 函数必须在运行时执行。它们是常规函数。当不在常量表达式的上下文中时,GCC 无法知道递归的深度。所以它不能内联它。本质上,GCC 必须能够在运行时和编译时上下文中应用该属性才能接受它。
  • 是的。当我使用常量表达式参数调用这个函数时,GCC 不会抱怨 [[gnu::always_inline]]。我试图解决的问题是,如果没有 [[gnu::always_inline]],在 main() 中调用函数实际上是内联的,但目标文件和最终的可执行文件仍然包含函数的未使用的非内联版本。
  • @AndrévonKugland - 尝试在整个事物周围添加一个未命名的命名空间。模板和内部链接说明符有时不会一直凝固。如果编译器能够真正证明它在其他 TU 中没有被使用,它也许能够抛弃死代码。
  • "GCC 无法判断这个运行时递归的深度。"。这不是真的。深度为 4,可以在编译时轻松查看。
【解决方案2】:

如果您检查生成的汇编代码,如果您删除 always_inlineflatten 属性,您可以看到 gcc 实际上正确内联了所有内容。

所以,这个问题是一个 QoI 问题。也许,在那个时候,当 always_inline 处理时,它不能被内联(因此出现错误消息),但 gcc 还是决定在之后内联它。

顺便说一句,你可以对 gcc 进行微调,对你的代码稍作修改,gcc 就可以编译它:

  • --param max-early-inliner-iterations=3 传递给gcc
  • 删除flatten 属性(不知道,为什么重要...)

(所以,实际上,这个问题与递归调用无关——从编译器的角度来看,函数是否递归并不重要,它只是遵循代码的流程——在一定程度上, 当然。这里递归深度只有 4,对于编译器来说并不难)

【讨论】:

    【解决方案3】:

    感谢grek40’s commentStoryTeller’s answer,这是我找到的解决方案。

    (至于我之前在编译后的二进制文件中留下未使用的函数模板实例的问题,我通过编译原始代码解决了这个问题——没有gnu::always_inlinegnu::flatten属性——带有参数-ffunction-sections -fdata-sections -Wl,--gc-sections。)

    现在reflect_mask_helper_0struct 内(因为C++ 不允许函数模板的部分特化),函数的i 参数变成struct 模板的Index 参数。

    #include <iostream>
    #include <limits.h>
    
    // End recursive template-expansion of function select below.
    template <typename Type>
    static inline constexpr Type select(unsigned index)
    { return Type(); }
    
    // Select one of the items passed to it.
    // e.g. select(0, a, b, c) = a; select(1, a, b, c) = b; etc.
    template <typename Type, typename... Params>
    [[gnu::always_inline]]
    static inline constexpr Type select(unsigned index, Type value, Params... values)
    { return index == 0 ? value : select<Type>(index - 1, values...); }
    
    template <typename Type>
    [[gnu::always_inline]]
    static inline constexpr Type reflect_mask_helper_1(Type mask, Type shift, Type value)
    { return ((value & mask) >> shift) | ((value << shift) & mask); }
    
    template <typename Type, unsigned Index>
    struct reflect_mask_helper_0
    {
      [[gnu::always_inline]]
      static inline constexpr Type invoke(Type value)
      {
        return reflect_mask_helper_0<Type, Index - 1>::call(
          reflect_mask_helper_1<Type>(
            static_cast<Type>(select(Index - 1,
              0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
              0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000)),
            1 << (Index - 1),
            value));
      }
    };
    
    template <typename Type>
    struct reflect_mask_helper_0<Type, 0>
    {
      [[gnu::always_inline]]
      static inline constexpr Type invoke(Type value) { return value; }
    };
    
    template <typename Type>
    static inline constexpr Type reflect_mask(Type value)
    { return reflect_mask_helper_0<Type, __builtin_ctz(sizeof(Type) * CHAR_BIT)>::invoke(value); }
    
    int main(void) {
      for (int i = 0; i < 65536; i++) {
        std::cout << reflect_mask<uint16_t>(i) << std::endl;
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-16
      • 2011-01-13
      • 2021-07-19
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 2012-10-08
      • 1970-01-01
      相关资源
      最近更新 更多