Lambda vs. manually inlined code changes GCC's optimizer behavior

Viewed 150

The following code:

#include <vector>

extern std::vector<int> rng;

int main()
{
  auto is_even=[](int x){return x%2==0;};
  int res=0;
  for(int x:rng){
    if(is_even(x))res+=x;
  }

  return res;
}

is optimized by GCC 11.1 (link to Godbolt) in a very different way than:

#include <vector>

extern std::vector<int> rng;

int main()
{
  int res=0;
  for(int x:rng){
    if(x%2==0)res+=x;
  }

return res;
}

(Link to Godbolt.) Besides, the second version (where the lambda has been replaced by direct, manual injection of its body in the place of call), is much faster than the first one.

Is this a GCC bug?

2 Answers

There is no such thing as a vectorized integral modulo operation in the x64 architecture. This means that the code by itself is not inherently vectorizable, and needs to be transformed beforehand before that can be done.

You can see the vectorization working just fine in both cases in the much easier case where a SIMD-friendly evenness test is used: https://godbolt.org/z/hc5ffbePY

So if anything, it could be argued that GCC managing to vectorize the inlined version at all, and clang inlining both of them, is actually pretty impressive.

That being said, since we know for a fact that GCC is capable of performing that transformation, it would appear that it is only performed before inlining happens, which is unfortunate, and probably deserves being brought up to the maintainer's attention.

It's a quirk of the code generation. There is no reason why the lambda version shouldn't be vectorized. In fact, clang vectorizes it as-is. If you specify return type as int, GCC vectorizes it too:

auto is_even = [](int x) -> int { return x % 2 == 0; };

If you use std::accumulate, it's also vectorized. You can report this to GCC so they can fix it.

Related