Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is gcc optimizing this loop?

So I encountered some strange behavior, which I stripped down to the following minimal example:

#include <iostream>
#include <vector>

int main()
{
  std::vector<int> vec;

  for(int i = 0; i < 1000; i++)
  {
    vec.push_back(2150000 * i);
    if(i % 100 == 0) std::cout << i << std::endl;
  }
}

When compiling with gcc 7.3.0 using the command

c++ -Wall -O2 program.cpp -o program

I get no warnings. Running the program produces the following output:

0
100
200
300
400
500
600
700
800
900
1000
1100
1200
1300

[ snip several thousand lines of output ]

1073741600
1073741700
1073741800
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

which I guess means that I finally ran out of memory for the vector.

Clearly something is wrong here. I guess this has something to do with the fact that 2150000 * 1000 is slightly larger than 2^31, but it's not quite as simple as that -- if I decrease this number to 2149000 then the program behaves as expected:

0
100
200
300
400
500
600
700
800
900

The cout isn't necessary to reproduce this behavior, so I suppose a minimal example is actually

#include <vector>

int main()
{
  std::vector<int> vec;

  for(int i = 0; i < 1000; i++)
  {
    vec.push_back(2150000 * i);
  }
}

Running this causes the program to wait for a long time and then crash.

Question

I'm fairly new to C++ at any serious level. Am I doing something stupid here that allows for undefined behavior, and if so, what? Or is this a bug in gcc?

I did try to Google this, but I don't really know what to Google.

Addendum

I see that (signed) integer overflow is undefined behavior in C++. To my understanding, that would only mean that the behavior of the expression

21500000 * i

is undefined -- i.e. that it could evaluate to an arbitrary number. That said, we can see that this expression is at least not changing the value of i.

like image 405
Daniel McLaury Avatar asked Oct 16 '18 16:10

Daniel McLaury


People also ask

Is GCC an optimizing compiler?

GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff. As compared to -O , this option increases both compilation time and the performance of the generated code.

What is GCC optimize?

The compiler optimizes to reduce the size of the binary instead of execution speed. If you do not specify an optimization option, gcc attempts to reduce the compilation time and to make debugging always yield the result expected from reading the source code.

How does compiler optimization work?

Compiler optimization is generally implemented using a sequence of optimizing transformations, algorithms which take a program and transform it to produce a semantically equivalent output program that uses fewer resources or executes faster.


1 Answers

To answer my own question, after examining the assembler output it looks like g++ optimizes this loop by changing

for(int i = 0; i < 1000; i++)
{
  vec.push_back(2150000 * i);
}

to something like

for(int j = 0; j < 1000 * 2150000; j += 2150000)
{
  vec.push_back(j);
}

I guess the addition is faster than doing a multiplication each cycle, and the rule about overflows being undefined behavior means that this change can be made without worrying about whether this introduces unexpected behavior if that calculation overflows.

Of course the conditional in the optimized loop always fails, so ultimately I end up with something more like

for(int j = 0; true; j += 2150000)
{
  vec.push_back(j);
}
like image 72
Daniel McLaury Avatar answered Sep 22 '22 09:09

Daniel McLaury