Home > front end >  Slow compilation speed for "const unordered_map" as global variable
Slow compilation speed for "const unordered_map" as global variable

Time:03-23

I am experiencing really slow compilation time, probably due to the existence of a global variable of the kind std::unordered_map. Below you can find the lines of code, which are located in a file called correspondance.h

    using Entry_t = std::tuple<Cfptr_t, short int, short int>;
    ...
    #include "map_content.h"
        inline const std::unordered_map<std::string, Entry_t> squaredampl{MAP_CONTENT};
    #undef MAP_CONTENT

and, in map_content.h there is the content of the map:

     #define MAP_CONTENT \
    { {EMPTYCHAR corr::su_R,ANTICHAR,EMPTYCHAR corr::sd_L,EMPTYCHAR corr::A,EMPTYCHAR corr::W},{    &mssm2to2::sumSqAmpl_su_R_anti_sd_L_to_A_W, 9,2}},\
    { {EMPTYCHAR corr::su_L,ANTICHAR,EMPTYCHAR corr::sd_R,EMPTYCHAR corr::A,EMPTYCHAR corr::W},{  &mssm2to2::sumSqAmpl_su_L_anti_sd_R_to_A_W, 9,2}},\
    ...

it goes on like this for about 3500 lines. Note that

  1. Cfptr_t is a function pointer
  2. EMPTYCHAR and all the variables of the type corr::something are integers, so they define the key value string

The problem I encounter is that the makefile takes 10 minutes for each file that includes correspondace.h, since indeed there are so many lines of code to process.

As you may have understood, this map is needed to allow the user to access functions without knowing their names, but just with a string. For this reason I cannot at the moment eliminate this feature, since it's fundamental.

As far as I could do, I tried to include this file in as few files as possible. Anyways, it is inevitable to include it in every file that will generate the executable, so, overall, this forces me to wait a long time each time I have to debug something.

If you have any suggestion on how to speed up compilation keeping this feature, it would be really helpful for me :)

CodePudding user response:

Change

#include "map_content.h"
inline const std::unordered_map<std::string, Entry_t> squaredampl{MAP_CONTENT};
#undef MAP_CONTENT

to

extern const std::unordered_map<std::string, Entry_t> squaredampl;

and in a new .cpp file:

#include "correspondance.h"
#include "map_content.h"
const std::unordered_map<std::string, Entry_t> squaredampl{MAP_CONTENT};
#undef MAP_CONTENT

This will cause only one definition of the map to be emitted (in contrast to inline which will emit it in every translation unit odr-using it).

It is safe as long as the map isn't used during the construction of another static storage duration object before main is entered. If that is required, it would be preferable to make the map a local static variable in a function returning it by-reference to be used by callers. This would avoid the "static initialization order fiasco".


It might also help to make a function to initialize the map with an insert statement for each entry instead of doing it all in the initializer, i.e. have a function initSquaredampl returning a std::unordered_map<std::string, Entry_t> by-value and then:

auto initSquaredampl() {
    std::unordered_map<std::string, Entry_t> squaredampl;
    // Insert items one-by-one
    return squaredampl;
}

const std::unordered_map<std::string, Entry_t> squaredampl = initSquaredampl();

This may help, because a compiler might not be optimized for compilation of very long expressions or initializers. However, they may also have trouble with very long functions.

CodePudding user response:

I want to thank @Kaldrr and @user17732522 for the nice insights. I solved my problem in the following way. In correspondance.h I just put:

inline std::unordered_map<std::string, Entry_t> squaredampl;

i.e. a simple initialisation of the empty map. Then, I created a new header init_map.h with the following lines:

#include "correspondance.h"

int Initialise();

and, of course, a correspondant initialise_map.cpp file with the definition of the function

#include "initialise_map.h"

int Initialise()
{
  if (!corr::squaredampl.empty()) return 0;
  corr::squaredampl.reserve(3500);
  corr::squaredampl = {
#include "map_content_body.h"
  };
  return 0;
}

where map_content_body.h are the lines of the content of the map. In this way:

  1. The code for the map is in only one file
  2. I avoided defining a macro and copying it
  3. Once that I have the object file initialise_map.o, all the .cpp files that will generate the executable will not contain the code, but the function will be there in linking phase

As a result, unless I remove initialise_map.o or I update initialise_map.cpp, my code compiles at a normal speed, as it should be.

Of course, I took care of having a #pragma once or similar in each header file to avoid useless code copies.

Many thanks also to @JaMiT for the encouraging words :)

  • Related