gcc 编译时间是否与执行次数或代码行数成正比?

Is gcc compile time proportional to number of executions or lines of code?

我正在使用 gcc 4.8.5 编译 c++98 代码。我的 C++ 代码静态初始化了 unoredred_maps 的 unordered_map,总共有 ~20,000 个键值对,以及需要 ~450 种不同类型的重载函数。该程序将在连续的数据流上执行,对于每个数据块,重载函数将 return 一个输出。

问题是,由于初始化 ~20,000 个键值对,gcc 编译时间太长。

嵌套的 unordered_map 具有 map< DATATYPE, map< key, value >> 的结构,每个数据输入只调用一个重载函数。换句话说,我不需要静态初始化整个嵌套映射,而是可以在需要时为相应的数据类型动态定义 map<key, value>。例如,我可以检查地图的定义,当它未定义时,我可以稍后在 运行 时间内填充它。这将生成一个具有约 45 个平均键值对的映射。

不过,我知道动态初始化需要更长的代码。对于上述简单执行(静态初始化整个地图),动态初始化等其他方法是否会显着减少时间?我的理解是,无论我采取什么替代方案,我仍然需要编写代码来填充整个键值对。此外,在大多数情况下,填充 unordered_map(散列图)之后的开销和实际计算不应渐进地不同,并且不应显示出与 运行 相同数量的循环以递增值的显着差异。

作为参考,我正在编写一个 python 脚本,该脚本读取多个 json 文件以打印出 C++ 代码,然后使用 gcc 对其进行编译。我不是直接从 c++ 读取 json 所以无论我做什么,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;
}

编辑:我最初的问题是 "Is CMAKE compile time proportional to..."。在评论的帮助下,用实际编译器名称 gcc 4.8.5 更改了术语 "CMAKE"。

根据您发布的更多代码,以及 Jonathan Wakely 对您的编译器特定问题的回答,我可以提出建议。

在编写自己的代码生成器时,如果可能的话,我更喜欢生成普通的旧数据并将逻辑和行为留在 non-generated 代码中。通过这种方式,您可以获得 data-driven 风格的小型(呃)纯 C++ 代码,以及声明式风格的独立哑块和 easy-to-generate 数据。

比如直接这样编码

// 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]); }
}

如果您真的想要,您甚至可以通过在中间 #include 将其从代码生成中分离出来,但这里可能没有必要。


原回答

Is CMAKE

CMake.

... compile time

CMake 配置构建系统,然后调用您的编译器。您没有告诉我们它正在为您配置哪个构建系统,但您可能 运行 手动为有问题的目标文件进行配置,并查看有多少开销是 CMake 的。

... proportional to number of executions or lines of code?

没有

有一些开销 per-execution。每个执行的编译器进程每行代码都有一些开销,但每次启用的优化可能会有更多开销,并且一些优化可能会随着圈复杂度或其他指标而扩展。

statically initializes unordered_map of unoredred_maps with ~20,000 total key-value pairs

你应该尽量隐藏你巨大的初始化 - 你没有显示任何代码,但如果它只在一个翻译单元中可见,那么只有一个目标文件将花费很长时间来编译。

您也可以使用像 gperf 这样的代码生成工具来构建完美的哈希。

如果至少没有看到您的实际代码片段以及关于您的文件和翻译单元的布局方式的一些提示,我无法为您提供更多详细信息。

旧版本的 GCC 需要很长时间来编译大型 initializer-lists,如下所示:

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

问题是initializer-list中的每个新元素都会导致更多的代码被添加到正在编译的块中,并且它变得越来越大,需要为编译器的AST分配越来越多的内存。最近的版本已更改为以不同方式处理 initializer-list,但仍然存在一些问题。由于您使用的是 GCC 4.8.5,因此最近的改进对您没有任何帮助。

However, I know that dynamic initialization will require longer code. For a simple execution described above (statically initializing entire map), will other method such as dynamic initialization significantly reduce time?

将大 initializer-list 拆分为插入元素 one-by-one 的单独语句 肯定会 减少使用旧版本 GCC 的编译时间。以这种方式可以非常快速地编译每个语句,而不必编译一个需要为每个元素分配越来越多内存的巨大初始化。