【问题标题】:Can I encourage g++ to inline a switch returning a sign?我可以鼓励 g++ 内联一个返回符号的开关吗?
【发布时间】:2019-07-16 15:52:06
【问题描述】:

我有一堆代码如下:

int sign(MyEnum e)
{
  switch(e)
  {
    case A:
    case B:
      return 1;
    case C:
    case D:
      return -1;
    default:
      throw std::runtime_error("Invalid enum value");
  }
}

int f(int a, int b, int c, MyEnum e)
{
  const int sign = sign(e);

  const int x = a * b - sign * c;
  const int y = a + sign * c;

  return x / y;
}

这里的算术只是一个例子。实际的代码更复杂,但重点是 sign 是 -1 或 1,具体取决于枚举值,我们进行了一系列计算,其中各种事物乘以 sign。 (编辑:枚举值在编译时未知。)

我希望优化这段代码,就好像我写了如下内容一样:

  int f(int a, int b, int c, MyEnum e)
  {
    switch(e)
    {
      case A:
      case B:
        {
          const int x = a * b - c;
          const int y = a + c;

          return x / y;
        }
      case C:
      case D:
        {
          const int x = a * b + c;
          const int y = a - c;

          return x / y;
        }
      default:
        throw new std::runtime_error("Invalid enum value");
    }
  }

当然,我实际上并不想编写所有这样的代码,因为这是测试和维护的噩梦。

使用编译器资源管理器,看起来sign 中的异常可能是这里的问题;如果我有“默认”案例返回,比如-1,那么我得到了我想要的。但我希望这里一些安全。

问题:

  1. 是否存在引发异常会阻止(或阻止编译器使用)这种优化的根本原因?
  2. 看起来在-O3 编译这个方法会复制两个方法,其中一个可以满足我的要求,尽管我不知道哪个会真正运行。我可以为此提供提示吗?
  3. 我不知道我是否想在-O3 编译所有内容。我可以只对特定代码块启用优化,还是鼓励编译器进行优化?
  4. 是否有一些花哨的模板元编程技巧或可以用来编写看起来像第一个块但生成的代码看起来像第二个块的代码的东西?
  5. 对于我正在尝试做的事情还有其他建议吗?

编辑:因为我(显然)不了解手头的所有问题,所以我可能没有给这个标题起个好名字。如果您知道自己在做什么,请随时编辑。

【问题讨论】:

  • 如果您不介意 GCC 特定的解决方案,那么您可以考虑 __attribute__((always_inline)),也许..
  • int sign(MyEnum) 函数是否被其他翻译单元使用?如果是,则它需要可用于链接。如果不是,它可能位于匿名命名空间中或标记为static
  • 你知道e在编译时的值是多少吗?
  • 关于第3点:有GCC-specifc __attribute__((hot)).
  • 在此上下文中,static 表示该函数是翻译单元的本地函数,并且不会链接到该翻译单元之外。这允许优化器执行更多优化并可能完全消除该功能(假设启用了优化)。

标签: c++ compiler-optimization


【解决方案1】:

这里有另一种看法:

template <int sign>
int f(int a, int b, int c)
{
  const int x = a * b - sign * c;
  const int y = a + sign * c;

  return x / y;
}


int f(int a, int b, int c, MyEnum e)
{
  const int sign = sign(e);
  if (sign == 1) return f<1>(a, b, c);
  else return f<-1>(a, b, c);
}

这样,您可以保持所需的安全性(以异常的形式),然后将结果信息转换为编译器可用于优化的编译时值。

正如Chris在cmets中指出的那样,如果sign只用于切换c的符号,您可以完全摆脱模板,只需在调用时翻转c的符号:

int f(int a, int b, int c)
{
  const int x = a * b - c;
  const int y = a + c;

  return x / y;
}


int f(int a, int b, int c, MyEnum e)
{
  const int sign = sign(e);
  if (sign == 1) return f(a, b, c);
  else return f(a, b, -c);
}

【讨论】:

  • 这里的模板参数有什么意义?传给f的时候不能直接取反吗?
  • @Chris 当然,如果该标志仅用于切换c 的标志,确实如此。但是,我发布的内容很笼统,并且由于“实际代码更复杂”,因此我想提供一个通用的解决方案。不过,我会在答案中添加注释。
【解决方案2】:

由于在这种情况下,int sign(MyEnum) 功能它没有被其他翻译单元使用,那么它可以被标记为static

在此上下文中,static 表示该函数是翻译单元的本地函数,并且不会链接到此翻译单元之外。 (关键字 static 在 C++ 中根据使用的上下文有不同的含义。)

这允许优化器执行更多优化并可能完全消除该功能(假设启用了优化)。

【讨论】:

  • 优化f时,不管sign是否为静态,编译器都有相同的优化可能性(关于f)。当然,如果sign被内联到f,那么可以去掉。
  • @geza • 是的,同意。如果标记为static,则编译器不必发出未使用的独立函数,该函数可能在链接时被其他翻译单元链接。 (那时可能会离开 LTO 或 WPO。或者如果没有链接到死代码,则被剥离。但那是另一回事了。与此同时,OP 的目标文件将它放在那里。)
猜你喜欢
  • 2011-10-20
  • 2019-08-22
  • 2019-02-07
  • 2011-08-16
  • 2010-12-16
  • 1970-01-01
  • 2015-06-30
  • 1970-01-01
  • 2018-09-18
相关资源
最近更新 更多