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

时间:2019-07-08 16:48:15

标签: c++ gcc compilation hashmap compile-time

我正在使用gcc 4.8.5编译c ++ 98代码。我的C ++代码使用总共约20,000个键值对和重载函数(约有450种不同类型)静态地初始化了unoredred_maps的unordered_map。该程序将在连续的数据流上执行,并且对于每个数据块,重载函数将返回输出。

问题在于,由于初始化了约20,000个键值对,gcc的编译时间太长。

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

但是,我知道动态初始化将需要更长的代码。对于上述的简单执行(静态初始化整个地图),其他方法(例如动态初始化)会大大减少时间吗?我的理解是,无论采取什么替代方法,我仍然需要编写代码来填充整个键值对。另外,在大多数情况下,填充unordered_map(hashmap)之后的开销和实际计算不应渐近地变化,并且与运行相同数量的循环以增加值相比,应该没有显着差异。

作为参考,我正在编写一个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;
}

编辑:我最初的问题是“ CMAKE编译时间是否与...成正比”。借助注释的帮助,将术语“ CMAKE”更改为实际的编译器名称gcc 4.8.5。

2 个答案:

答案 0 :(得分:4)

有了您发布的更多代码,以及乔纳森·韦克利(Jonathan Wakely)对编译器特定问题的回答,我可以提出建议。

在编写自己的代码生成器时,如果可能的话,我更喜欢生成简单的旧数据,并将逻辑和行为保留在未生成的代码中。这样,您就可以以数据驱动的样式获得一个较小的纯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]); }
}

如果您真的想,只需在中间#include将其与代码源分开,就可以将其与逻辑分离开来,但是在这里可能没有必要。


原始答案

  

是CMAKE

CMake。

  

...编译时间

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

  

...与执行次数或代码行成比例吗?

否。

每次执行会有一些开销。每个执行的编译器进程每行代码都有一些开销,但每个启用的优化可能会有更多开销,并且某些优化可能随循环复杂性或其他度量标准而扩展。

  

静态地初始化具有约20,000个键值对的unoredred_maps的unordered_map

您应该尽可能地隐藏巨大的初始化-您没有显示任何代码,但是如果仅在一个翻译单元中可见,则仅编译一个目标文件将花费很长时间。

您还可能使用gperf之类的代码生成工具来构建完美的哈希。

在没有看到您的实际代码的至少一部分以及有关文件和翻译单元布局方式的提示时,我无法为您提供更多细节。

答案 1 :(得分:2)

GCC的旧版本需要很长时间才能编译如下所示的大型初始化列表:

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

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

  

但是,我知道动态初始化将需要更长的代码。对于上述的简单执行(静态初始化整个地图),其他方法(例如动态初始化)会大大减少时间吗?

将较大的初始化器列表拆分为单独的语句,这些语句一个接一个地插入元素,将确定,从而减少了使用旧版GCC时的编译时间。这样就可以很快地编译每个语句,而不必只编译一个巨大的初始化,而每个初始化都需要为每个元素分配越来越多的内存。