首页 > 解决方案 > gcc 编译时间是否与执行次数或代码行数成正比?

问题描述

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

问题是,由于初始化了大约 20,000 个键值对,gcc 编译时间过长。

嵌套的 unordered_map 的结构为map< DATATYPE, map< key, value >>,每个数据输入只调用一个重载函数。换句话说,我不需要静态初始化整个嵌套映射,而是可以map<key, value>在需要时为相应的数据类型动态定义。例如,我可以检查地图的定义,当它是 undefined 时,我可以稍后在运行时填充它。这将产生一个具有约 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 编译时间是否与...成正比”。在注释的帮助下,用实际的编译器名称 gcc 4.8.5 更改了术语“CMAKE”。

标签: c++gcccompilationhashmapcompile-time

解决方案


通过您发布的更多代码以及 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在中间 ing 来将逻辑从你的代码生成中分离出来,但这里可能没有必要。


原始答案

是 CMAKE

制作。

...编译时间

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

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

不。

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

静态初始化 unoredred_maps 的 unordered_map,总共约 20,000 个键值对

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

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

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


推荐阅读