I'm working on loading PNG files. I know there are existing libraries, but I'm doing this for learning purposes. Previously, I was using LodePNG, which is a great library that performs really well.

I have a large PNG, 2048x2048 pixels with 8 bits of depth. I load it ten times with my code, and average out the time it takes (measured with std::chrono). I do the same with LodePNG. LodePNG is built in the solution so it's using the same compiler flags as my own code.

Initially I found that my library would take ~8000ms on average to load the image whereas LodePNG takes on average ~800ms. 10 times longer!

Using this technique I have managed to reduce my execution time down to ~3100ms. This was through a series of improvements:

  1. Used std::uint_fast32_t sort of types in place of std::uint32_t where it made sense
  2. Refactoring some parts of code several times
  3. Replacing some std::maps with std::arrays or std::vectors where I could (I naively used maps when it wasn't required)
  4. Enabled Link-Time Optimisation (which didn't help LodePNG speed but shaved 100ms off mine)

However, I've found now that the random interrupting and checking the backtrace in GDB is revealing the same bits of code over and over again:

  1. Calls to std::copy
  2. Calls to the creation of std::vectors (I'm using a custom memory allocator that performs better, it's also being used for pointer creation)

Which aren't things I can improve upon. LodePNG is mostly C-like code, it doesn't use any Standard Library headers. But, I always read that using these libraries shouldn't be any slower... but well here I am.

My question is, what else can I do to identify hot-spots and fix them? Any techniques that would be appropriate? Ideally they would work on Windows.

I'm using GCC 10.2 through MingW64 in MSYS2.

  • 1
    Questions about coding and low-level optimization techniques are a way better fit for Stackoverflow than for this site (but when you decide to move it over, don't forget to delete your question here first, for not making a cross-post).
    – Doc Brown
    Apr 23 '21 at 4:44
  • 1
    ... moreover, asking for tool recommendations is off-topic on this site, as well as on Stackoverflow.
    – Doc Brown
    Apr 23 '21 at 4:45
  • 2
    FWIW, when your profiling technique revealed std::copy and std::vector::ctor as a bottleneck, maybe you should look for ways to reduce the number of calls to these methods? For example, by doing more things inline? This is pretty hard to tell without seeing the code. But don't understand my comment as a request to post your code here - codereview.stackexchange may be the best place for this.
    – Doc Brown
    Apr 23 '21 at 4:59
  • 1
    Manually interrupting and checking backtraces in GDB is a "poor man's profiler". So get yourself a decent profiler (I hope that exists for C++) that can show you the call tree over the total runtime, annotated with the percentage of time spent in the various functions and (optionally) the number of calls to that function. Identify the bottlenecks (including the callers chain where they are called from), and then think about either local optimizations or about algorithm improvements. Apr 23 '21 at 10:29
  • Did you try to make use of "vector::reserve" to avoid the number of reallocations?
    – Doc Brown
    Apr 23 '21 at 12:59

The advice that the C++ standard libraries aren't slower than C code is true when the same operations are performed.

A quick look at the LodePNG code shows that is loads a file like this:

  1. determine the file size
  2. allocate a buffer large enough to store the entire file
  3. read the file into the buffer in one go

Based on your description, I assume you are loading a file like this:

  1. start with an empty std::vector
  2. while not eof
    1. read one (or a few) bytes
    2. append the read byte(s) to the vector

These algorithms are not equivalent and may thus give a different performance.

A lot of the std::copy calls may come from re-allocations that the vector does while it is growing in size. But if you know what the final size of the vector will be, you can prevent all of those by telling the vector up front to reserve enough memory to store the total number of elements needed.

Such algorithmic changes are what you should first look at when you want to improve the performance of your code, as that is where the biggest wins can be made.

Not the answer you're looking for? Browse other questions tagged or ask your own question.