【问题标题】:Is gcc compile time proportional to number of executions or lines of code?gcc 编译时间是否与执行次数或代码行数成正比?
【发布时间】:2019-11-18 05:34:11
【问题描述】:

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

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

嵌套的 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 编译时间是否与...成正比”。在 cmets 的帮助下,用实际的编译器名称 gcc 4.8.5 更改了术语“CMAKE”。

【问题讨论】:

  • CMake 不是编译器,因此 CMake 不会编译代码。
  • 不,没有一般规律,规定 编译时间 = 5 秒 * 代码行数 左右。这取决于很多事情。
  • 这篇文章背后有一个很好的问题,即如何提高特定数据结构的编译时间。但也有一些非常令人分心的误解隐藏了这个好问题。你能edit你的帖子并用你的实际编译器(即g++、clang++、msvc等)替换CMake吗?
  • @alterigel 和 nada,我编辑了帖子,谢谢

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


【解决方案1】:

根据您发布的更多代码以及 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]); }
}

如果您真的想要,您甚至可以通过在中间#includeing 将逻辑与代码生成分开,但这里可能没有必要。


原答案

是 CMAKE

CMake。

...编译时间

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

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

没有。

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

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

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

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

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

【讨论】:

  • 您好,我进行了编辑。你觉得可以再看一遍吗?
  • 你还没有显示布局:是头文件中的初始化吗?如果是这样,请尝试将其隐藏在 .cpp 文件中。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 嗨,谢谢你,这对我有很大帮助。因此,如果我改为创建 20,000 行 exampleMap.insert() 会加快编译时间吗?
  • 是的,应该可以。编译仍然需要一段时间,因为这仍然是一个大文件,但它不会导致 AST 像初始化列表那样爆炸。
  • 您好,这里有更新;不幸的是,对所有 20,000 对使用 tempMap.insert({key1, val1}) 然后 exampleMap.insert({datatype, tempMap}) 需要更长的时间
  • 呃,对不起,这让我很吃惊。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-10
  • 2011-04-17
  • 2020-04-12
  • 2010-09-19
  • 1970-01-01
  • 2020-06-30
  • 1970-01-01
相关资源
最近更新 更多