Время компиляции gcc пропорционально числу выполнений или строк кода? - PullRequest
0 голосов
/ 08 июля 2019

Я использую gcc 4.8.5 для компиляции кода c ++ 98.Мой код на С ++ статически инициализирует unordered_map для unoredred_maps с ~ 20 000 пар ключей-значений и перегруженной функцией, которая будет принимать ~ 450 различных типов.Эта программа будет выполняться в непрерывном потоке данных, и для каждого блока данных перегруженная функция будет возвращать вывод.

Проблема в том, что gcc компилируется слишком долго из-за инициализации ~ 20 000 значений ключаpair.

Вложенная unordered_map имеет структуру map< DATATYPE, map< key, value >>, и для каждого ввода данных вызывается только одна из перегруженных функций.Другими словами, мне не нужно статически инициализировать всю вложенную карту, но вместо этого я могу динамически определять map<key, value> для соответствующего типа данных, когда это необходимо.Например, я могу проверить определение карты и , когда она не определена , позже я могу заполнить ее во время выполнения.Это приведет к отображению карты с ~ 45 средними парами ключ-значение.

Однако я знаю, что для динамической инициализации потребуется более длинный код.Для простого выполнения, описанного выше (статическая инициализация всей карты), значительно ли уменьшит время другой метод, такой как динамическая инициализация?Насколько я понимаю, какую бы альтернативу я ни выбрал, мне все равно нужно написать код для заполнения целых пар ключ-значение.Кроме того, накладные и фактические вычисления, которые идут за заполнением unordered_map (hashmap), не должны отличаться асимптотически в большинстве случаев и не должны показывать значительную разницу, чем выполнение одинакового числа циклов для увеличения значения.

Для справки, япишу скрипт на python, который читает несколько файлов json для распечатки кода на c ++, который затем компилируется с использованием gcc.Я не читаю json напрямую из c ++, поэтому что бы я ни делал, исходнику c ++ нужно будет вставлять значение ключа один за другим, потому что у него не будет доступа к файлу json.

// below is someEXE.cpp, which is a result from python script. 
// Every line is inside python's print"" (using python 2.7) 
// so that it can write complete c++ that should  compile.

someEXE.cpp

// example of an overloaded function among ~450
// takes in pointer to data and exampleMap created above
void exampleFunction(DIFFERENT_TYPE1*data, 
std::unorderd_map<std::string, std::unordered_map<std::string, std::string>> exampleMap) {
   printf("this is in specific format: %s", exampleMap["DATATYPE1"] 
   [std::to_string(data->member_variable)].c_str();
   //... more print functions below (~25 per datatype)
};

int main() {

   // current definition of the unordered_map (total ~20,000 pairs)
   std::unordered_map<std::string, std::unordered_map<std::string, 
   std::string>> exampleMap = {
       {"DATATYPE1", {{"KEY1", "VAL1"}, {"KEY2", "VAL2"}, /*...*/}}
   };

   // create below test function for all ~450 types
   // when I run the program, code will printf values to screen
   DIFFERENT_TYPE1 testObj = {0};
   DIFFERENT_TYPE1 *testObjPointer = &testObj;
   exampleFunction(testObjPointer, exampleMap);

   return 0;
}

РЕДАКТИРОВАТЬ: Мой первоначальный вопрос былMsgstr "Время компиляции CMAKE пропорционально ...".Изменен термин «CMAKE» с фактическим именем компилятора, gcc 4.8.5 с помощью комментариев.

Ответы [ 2 ]

4 голосов
/ 08 июля 2019

С дополнительным кодом, который вы разместили, и ответом Джонатана Уэйкли на конкретную проблему с вашим компилятором, я могу высказать предположение.

При написании собственного кода, по возможности, я предпочитаю генерировать простые старые данные иоставляя логику и поведение в не сгенерированном коде.Таким образом, вы получите небольшой (er) чистый код C ++ в стиле управляемых данными, а также отдельный блок глупых и легко генерируемых данных в декларативном стиле.

Например, непосредственно кодируйте этот

// GeneratedData.h
namespace GeneratedData {
  struct Element {
    const char *type;
    const char *key;
    const char *val;
  };

  Element const *rawElements();
  size_t rawElementCount();
}

и это

// main.cpp
#include "GeneratedData.h"

#include <string>
#include <unordered_map>

using Map = std::unordered_map<std::string, std::string>;
using TypeMap = std::unordered_map<std::string, Map>;

TypeMap buildMap(GeneratedData::Element const *el, size_t count)
{
  TypeMap map;
  for (; count; ++el, --count) {
    // build the whole thing here
  }
}
// rest of main can call buildMap once, and keep the big map.
// NB. don't pass it around by value!

и, наконец, сгенерируйте большой тупой файл

// GeneratedData.cpp
#include "GeneratedData.h"

namespace {
  GeneratedData::Element const array[] = {
    // generated elements here
  };
}

namespace GeneratedData {
  Element const *rawElements { return array; }
  size_t rawElementCount() { return sizeof(array)/sizeof(array[0]); }
}

, если вы действительно хотите, вы можете отделить даже этологика из вашего codegen просто #include в середине, но это, вероятно, не нужно здесь.


Оригинальный ответ

CMAKE

CMake.

... время компиляции

CMake настраивает систему сборки, которая затем вызывает ваш компилятор.Вы не сказали нам, какую систему сборки она настраивает для вас, но вы, вероятно, могли бы запустить ее вручную для проблемного объектного файла (ов) и посмотреть, сколько на самом деле накладных расходов у CMake.

... пропорционально числу выполнений или строк кода?

Нет.

Существуют некоторые накладные расходы на выполнение.Каждый выполняемый процесс компилятора имеет некоторые издержки на строку кода, но, вероятно, гораздо больше накладных расходов на включенную оптимизацию, и некоторые оптимизации могут масштабироваться с цикломатической сложностью или другими метриками.

статически инициализирует unordered_map unoredred_maps с ~ 20 000Всего пар ключ-значение

Вы должны попытаться как можно больше скрыть свою гигантскую инициализацию - вы не показали никакого кода, но если он виден только в одной единице перевода, только один объектный файл будеткомпиляция займет очень много времени.

Вы также можете использовать инструмент codegen, такой как gperf , для создания идеального хэша.

Я не могу дать вам многобольше деталей, не видя хотя бы фрагмента вашего фактического кода и некоторого намека на то, как расположены ваши файлы и единицы перевода.

2 голосов
/ 08 июля 2019

В старых версиях GCC очень долго собирались большие списки инициализаторов, например:

unordered_map<string, unordered_map<string, string>> exampleMap = {
    {"DATATYPE1", {{"KEY1", "VAL1"}, {"KEY2", "VAL2"}, /*...*/}}
};

Проблема в том, что каждый новый элемент в списке инициализаторов приводит к добавлению большего количества кода в компилируемый блок, и он становится все больше и больше, что требует выделения все большего объема памяти для AST компилятора. Последние версии были изменены, чтобы по-другому обрабатывать список инициализаторов, хотя некоторые проблемы все еще остаются. Поскольку вы используете GCC 4.8.5, недавние улучшения вам все равно не помогут.

Однако я знаю, что для динамической инициализации потребуется более длинный код. Для простого выполнения, описанного выше (статическая инициализация всей карты), значительно ли уменьшит время другой метод, такой как динамическая инициализация?

Разделение большого списка инициализаторов на отдельные операторы, вставляющие элементы один за другим, определенно сократит время компиляции при использовании более старых версий GCC. Таким образом, каждый оператор может быть скомпилирован очень быстро, вместо того, чтобы компилировать одну огромную инициализацию, которая требует выделения все большего количества памяти для каждого элемента.

...