Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a bunch of code like the following:

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

The arithmetic here is just an example. The actual code is more complex, but the point is that sign is either -1 or 1 depending on an enum value, and we do a bunch of calculations where various things are multiplied by sign. (Edit: the enum value is not known at compile time.)

I'd like this code to be optimized as though I'd written something like the following:

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

Of course I don't actually want to write all the code like that, because it's a testing and maintenance nightmare.

Playing around with Compiler Explorer, it looks like the exception in sign may be the issue here; if I have the "default" case return, say, -1, then I get what I want. But I would like some safety here.

Questions:

  1. Is there a fundamental reason that throwing an exception prevents (or discourages the compiler from using) this optimization?
  2. It looks like compiling this at -O3 makes two clones of the method, one of which does what I want, although I don't know which one actually would get run. Can I give hints for this?
  3. I don't know if I want to compile everything at -O3. Can I turn on optimizations for just a particular block of code, or encourage the compiler to make them?
  4. Is there some fancy template metaprogramming trick or something I can use to write code that looks like the first block but which generates code looking like the second block?
  5. Any other suggestions for what I'm trying to do?

EDIT: Since I (obviously) don't understand all the issues at hand I probably didn't give this a great title. Please feel free to edit if you know what you're doing.

like image 952
Daniel McLaury Avatar asked Jul 16 '19 15:07

Daniel McLaury


People also ask

Will a return break a switch statement?

Yes, you can use return instead of break ... break is optional and is used to prevent "falling" through all the other case statements. So return can be used in a similar fashion, as return ends the function execution.

When should we use inline and when shouldn't give an example?

One should use the inline function qualifier only when the function code is small. If the functions are larger you should prefer the normal functions since the saving in memory space is worth the comparatively small sacrifice in execution speed.

When should you make an inline function?

Inline functions are commonly used when the function definitions are small, and the functions are called several times in a program. Using inline functions saves time to transfer the control of the program from the calling function to the definition of the called function.

Why would you want to use inline function?

An inline function is one for which the compiler copies the code from the function definition directly into the code of the calling function rather than creating a separate set of instructions in memory. This eliminates call-linkage overhead and can expose significant optimization opportunities.


2 Answers

Here's an alternative take on the thing:

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

This way, you keep the safety you want (in the form of the exception), but then transform the resulting information into a compile-time value which the compiler can use for optimisations.

As Chris pointed out in comments, if sign is only ever used to switch the sign of c, you can get rid of the template altogether and just flip c's sign when calling:

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);
}
like image 200
Angew is no longer proud of SO Avatar answered Sep 26 '22 21:09

Angew is no longer proud of SO


Since in this situation, int sign(MyEnum) function it not used by other translation units, then it could be marked static.

In this context, static means that the function is local to the translation unit, and does not link outside of this translation unit. (The keyword static has different meanings in C++ depending on the context it is used.)

That allows the optimizers to perform more optimizations and possibly eliminate the function entirely (assuming optimization is enabled).

like image 42
Eljay Avatar answered Sep 22 '22 21:09

Eljay