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

问题描述 投票:0回答:3

我有一堆如下代码:

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. 我不知道是否要在
  4. -O3处编译所有
    。我可以仅针对特定代码块打开优化,或者鼓励编译器进行优化吗?
  5. 是否有一些奇特的模板元编程技巧或者我可以用来编写看起来像第一个块但生成看起来像第二个块的代码的东西?
  6. 对于我正在尝试做的事情还有其他建议吗?

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

c++ compiler-optimization
3个回答
6
投票
这是对这件事的另一种看法:

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

3
投票
由于在这种情况下,

int sign(MyEnum)

功能没有被其他翻译单元使用,那么可以将其标记为
static

在此上下文中,

static

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

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


0
投票
由于您希望编译器内联该行为,因此假设您在编译时知道枚举的值。在这种情况下,您可以使用模板专门化来根据枚举的值使用所需的逻辑。

int f(int a, int b, int c) { const int x = a * b - c; const int y = a + c; return x / y; } enum MyEnum { A, B, C, D }; // Template definition with default behavior. template<MyEnum e> int correct_c(int c) { throw std::runtime_error("Invalid enum value"); return 0; }; // Specialized templates according to the value of e. template<> int correct_c<A>(int c) { return c; } template<> int correct_c<B>(int c) { return c; } template<> int correct_c<C>(int c) { return -c; } template<> int correct_c<D>(int c) { return -c; }
用途:

void test() { int rA = f(7, 2, correct_c<A>(3)); int rB = f(7, 2, correct_c<B>(3)); int rC = f(7, 2, correct_c<C>(3)); int rD = f(7, 2, correct_c<D>(3)); int rUnknown = 0; try { rUnknown = f(7, 2, correct_c<static_cast<MyEnum>(4)>(3)); } catch (const std::runtime_error& e) { cout << "Runtime error caught \"" << e.what() << "\"\n"; } cout << "Results: \n" "rA: " << rA << "\n" "rB: " << rB << "\n" "rC: " << rC << "\n" "rD: " << rD << "\n" "rUnknown: " << rUnknown << '\n'; }
(请注意,我将 

c

 的逻辑移到了 
f
 之外,以避免 
逻辑内聚。)

编译器会将调用内联到

correct_c

这也将阻止编译非枚举值,除非您明确地强制转换它们。

如果您需要支持

e

 的运行时评估,您可以编写一个特定函数,该函数将 
MyEnum
 值作为参数而不是模板参数,并将其路由到适当的专用模板函数。

int correct_c(int c, MyEnum e) { switch (e) { case A: return correct_c<A>(c); case B: return correct_c<B>(c); case C: return correct_c<C>(c); case D: return correct_c<D>(c); default: throw std::runtime_error("Invalid enum value"); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.