我可以鼓励 g++ 内联一个返回符号的开关吗?

Can I encourage g++ to inline a switch returning a sign?

我有一堆代码如下:

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");
    }
  }

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

使用 Compiler Explorer,看起来 sign 中的异常可能是这里的问题;如果我有 "default" 案例 return,比方说,-1,那么我得到了我想要的。但我希望这里一些安全。

问题:

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

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

这里有一个替代方案:

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);
}

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

正如 Chris 在评论中指出的那样,如果 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);
}

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

在此上下文中,static 表示该函数是翻译单元的本地函数,而不是 link 此翻译单元之外的函数。 (关键字 static 在 C++ 中具有不同的含义,具体取决于它所使用的上下文。)

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