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

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

本文关键字:符号 开关 返回 g++ 我可以      更新时间:2023-10-16

我有一堆代码,如下所示:

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. 对我正在尝试做的事情还有其他建议吗?

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

这是对这件事的另一种看法:

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

由于在这种情况下,int sign(MyEnum)其他翻译单元不使用的功能,因此可以将其标记为static

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

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

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

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

(注意,我将处理ce依赖的逻辑移到了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");
}
}