【问题标题】:GCC compilation very slow (large file)GCC 编译很慢(大文件)
【发布时间】:2015-10-30 19:34:42
【问题描述】:

我正在尝试编译一个大型 C 文件(专门用于 MATLAB 混合)。 C 文件大小约为 20 MB(如果您想使用它,可以使用 from the GCC bug tracker)。

下面是我正在运行的命令和屏幕的输出。这已经运行了几个小时,如您所见,优化已被禁用(-O0)。为什么这么慢?有什么方法可以让这更快吗?

(供参考:Ubuntu 12.04(Precise Pangolin)64 位和 GCC 4.7.3)

/usr/bin/gcc -c -DMX_COMPAT_32   -D_GNU_SOURCE -DMATLAB_MEX_FILE  -I"/usr/local/MATLAB/R2015a/extern/include" -I"/usr/local/MATLAB/R2015a/simulink/include" -ansi -fexceptions -fPIC -fno-omit-frame-pointer -pthread -O0 -DNDEBUG path/to/test4.c -o /tmp/mex_198714460457975_3922/test4.o -v
Using built-in specs.
COLLECT_GCC=/usr/bin/gcc
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.3-2ubuntu1~12.04' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --with-system-zlib --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.7.3 (Ubuntu/Linaro 4.7.3-2ubuntu1~12.04)
COLLECT_GCC_OPTIONS='-c' '-D' 'MX_COMPAT_32' '-D' '_GNU_SOURCE' '-D' 'MATLAB_MEX_FILE' '-I' '/usr/local/MATLAB/R2015a/extern/include' '-I' '/usr/local/MATLAB/R2015a/simulink/include' '-ansi' '-fexceptions' '-fPIC' '-fno-omit-frame-pointer' '-pthread' '-O0' '-D' 'NDEBUG' '-o' '/tmp/mex_198714460457975_3922/test4.o' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -quiet -v -I /usr/local/MATLAB/R2015a/extern/include -I /usr/local/MATLAB/R2015a/simulink/include -imultilib . -imultiarch x86_64-linux-gnu -D_REENTRANT -D MX_COMPAT_32 -D _GNU_SOURCE -D MATLAB_MEX_FILE -D NDEBUG path/to/test4.c -quiet -dumpbase test4.c -mtune=generic -march=x86-64 -auxbase-strip /tmp/mex_198714460457975_3922/test4.o -O0 -ansi -version -fexceptions -fPIC -fno-omit-frame-pointer -fstack-protector -o /tmp/ccxDOA5f.s
GNU C (Ubuntu/Linaro 4.7.3-2ubuntu1~12.04) version 4.7.3 (x86_64-linux-gnu)
    compiled by GNU C version 4.7.3, GMP version 5.0.2, MPFR version 3.1.0-p3, MPC version 0.9
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/MATLAB/R2015a/extern/include
 /usr/local/MATLAB/R2015a/simulink/include
 /usr/lib/gcc/x86_64-linux-gnu/4.7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
GNU C (Ubuntu/Linaro 4.7.3-2ubuntu1~12.04) version 4.7.3 (x86_64-linux-gnu)
    compiled by GNU C version 4.7.3, GMP version 5.0.2, MPFR version 3.1.0-p3, MPC version 0.9
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: c119948b394d79ea05b6b3986ab084cf

编辑:后续:我遵循 chqrlie 的建议,tcc 在

但是,当尝试对其进行 mex 时,mex 通常需要另一个命令。第二个命令通常是:

/usr/bin/gcc -pthread -Wl,--no-undefined -Wl,-rpath-link,/usr/local/MATLAB/R2015a/bin/glnxa64 -shared  -O -Wl,--version-script,"/usr/local/MATLAB/R2015a/extern/lib/glnxa64/mexFunction.map" /tmp/mex_61853296369424_4031/test4.o   -L"/usr/local/MATLAB/R2015a/bin/glnxa64" -lmx -lmex -lmat -lm -lstdc++ -o test4.mexa64

我不能用 tcc 运行它,因为其中一些标志不兼容。如果我尝试使用 GCC 运行第二个编译步骤,我会得到:

/usr/bin/ld: test4.o: relocation R_X86_64_PC32 against undefined symbol `mxGetPr' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status

编辑:解决方案似乎是铿锵声。 tcc可以编译文件,但是mexing第二步的参数和tcc的参数选项不兼容。 Clang 速度非常快,可以生成一个漂亮、小巧、优化的文件。

【问题讨论】:

  • 这个C代码真的很奇怪。你能考虑生成不同的 C 吗?
  • @user650261 我不会为了回答 Stack Overflow 上的问题而购买 2000 美元的软件包。如果你需要帮助,约定是做一个独立的例子。您的代码不是独立的,因为它需要专有的 mex.h 标头。 20 MiB C 表达式绝对是奇怪的。也许可以用不同的方式表达相同的概念,例如作为一个从数组中提取参数的循环。
  • @user650261 请引用您的“大公司编译大文件”的来源。我敢打赌,他们将它们分成更小的可管理模块,因为它们肯定知道“获得的智慧”。
  • 我不会从外部且可能不为人知的来源下载源代码。但是,我第二次选择 FUZxxl 和 WeatherVane。如此大的文件绝对不正常,不仅要维护,还要编辑和(可能)调试。 @FUZxxl 的评论显然是有道理的,而不是你告诉他闭嘴的。是 寻求帮助,显然文件大小有问题。尝试将其分解为更小的单位。
  • 好的,我认为文件的大小是这里固有的问题,其他人可能也有类似的问题,所以我重新打开了这个。但是,我担心链接文件会在未来某个时候消失,从而使问题变得不那么有用。这就是为什么我们倾向于建议人们尽量减少问题并将其放在问题本身中。这是一个有点极端的情况。我只要求每个人都保持他们的 cmets 文明和主题。

标签: c gcc mex


【解决方案1】:

几乎整个文件都是一个表达式,即对double f[24] = ... 的赋值。这将生成一个 巨大的 抽象语法树。如果没有专门的编译器可以有效地处理这个问题,我会感到惊讶。

20 兆字节的文件本身可能没问题,但一个巨大的表达式可能是导致问题的原因。尝试作为初步步骤,将行拆分为 double f[24] = {0},然后分配 24 个 f[0] = ...; f[1] = ...,看看会发生什么。最坏的情况是,您可以将 24 个分配拆分为 24 个函数,每个函数都在其自己的 .c 文件中,并分别编译它们。这不会减少 AST 的大小,它只会重新组织它,但 GCC 在处理许多语句时可能会更加优化,这些语句加起来会产生大量代码,而不是一个巨大的表达式。

最终的方法是以更优化的方式生成代码。例如,如果我搜索s4*s5*s6,我会得到 77,783 次点击。这些s[4-6] 变量不会改变。您应该生成一个临时变量double _tmp1 = s4*s5*s6;,然后使用它而不是重复表达式。您刚刚从抽象语法树中删除了 311,132 个节点(假设 s4*s5*s6 是 5 个节点,_tmp1 是一个节点)。这就是 GCC 必须做的少得多的处理。这也应该生成更快 代码(您不会重复相同的乘法 77,783 次)。

如果您以递归方式以智能方式执行此操作(例如s4*s5*s6 --> _tmp1(c4*c6+s4*s5*s6) --> (c4*c6+_tmp1) --> _tmp2c5*s6*(c4*c6+s4*s5*s6) --> @ 987654336@ -> _tmp3 等...),您可能可以消除生成代码的大部分大小。

【讨论】:

  • 我只是指出 C11 草案标准,5.2.4.1 Translation limits, Section 1 [...]4095 characters in a logical source line[...]。如果这是一个单个 20MB 表达式,则编译器根本不需要能够编译它。
  • @EOF Op 可以将表达式分成多行以规避此限制,但他的代码还违反了其他限制。
  • 感谢您的回复。我曾考虑尝试通过预先计算来简化它——这是我计划采取的后续步骤,但如果这能解决问题,我现在可能会这样做。我想知道您是否可以更多地谈论解析树机制 - 为什么解析需要这么长时间?我注意到编译可以非常快速地检测语法错误,那么为什么在这种情况下解析编译需要这么长时间。很抱歉这些问题 - 我只是想了解导致问题的原因,所以我不会陷入变化的兔子洞,只是为了达到死胡同。
  • @user650261:嗯,我可能错了,因为解析需要这么长时间。我在文件末尾卡住了一个解析错误,它在几秒钟内就捕获了它。除非它以某种方式优化以捕获这些错误而不先解析它,我对此表示怀疑。正如 FUZxxl 所说,可能是解析树上的转换占用了这么多时间。
  • 从我的实验来看,gcc的解析阶段大约需要10秒,解析树分析和代码生成必须使用复杂度高于O(n)的算法,可能是O(n*2)或更糟,它实际上需要几个小时才能完成。 tcc 不构建解析树,它在单次传递中动态生成代码。输出非常大(42MB 的代码+数据),但它的速度非常快,甚至 38MB 的迭代代码也应该在适当的时间内执行,远少于 1 秒。
【解决方案2】:

经过测试,我发现 Clang 编译器编译大文件的问题似乎较少。尽管 Clang 在编译过程中消耗了将近 1 GB 的内存,但它成功地将 OP 的源代码形式转换为 70 kB 的目标文件。这适用于我测试的所有优化级别。

gcc 还能够快速编译此文件,并且如果打开优化,则不会消耗太多内存。这个bug in gcc 来自 OPs 代码中的大表达式,这给寄存器分配器带来了巨大的负担。启用优化后,编译器会执行一种称为通用子表达式消除的优化,它能够从 OP 代码中移除大量冗余,从而将编译时间和目标文件大小减少到可管理的值。

以下是上述错误报告中的测试用例的一些测试:

$ time gcc5 -O3 -c -o testcase.gcc5-O3.o testcase.c
real    0m39,30s
user    0m37,85s
sys     0m1,42s
$ time gcc5 -O0 -c -o testcase.gcc5-O0.o testcase.c
real    23m33,34s
user    23m27,07s
sys     0m5,92s
$ time tcc -c -o testcase.tcc.o testcase.c
real    0m2,60s
user    0m2,42s
sys     0m0,17s
$ time clang -O3 -c -o testcase.clang-O3.o testcase.c
real    0m13,71s
user    0m12,55s
sys     0m1,16s
$ time clang -O0 -c -o testcase.clang-O0.o testcase.c
real    0m17,63s
user    0m16,14s
sys     0m1,49s
$ time clang -Os -c -o testcase.clang-Os.o testcase.c
real    0m14,88s
user    0m13,73s
sys 0m1,11s
$ time clang -Oz -c -o testcase.clang-Oz.o testcase.c
real    0m13,56s
user    0m12,45s
sys     0m1,09

这是生成的目标文件大小:

    text       data     bss      dec        hex filename
39101286          0       0 39101286    254a366 testcase.clang-O0.o
   72161          0       0    72161      119e1 testcase.clang-O3.o
   72087          0       0    72087      11997 testcase.clang-Os.o
   72087          0       0    72087      11997 testcase.clang-Oz.o
38683240          0       0 38683240    24e4268 testcase.gcc5-O0.o
   87500          0       0    87500      155cc testcase.gcc5-O3.o
   78239          0       0    78239      1319f testcase.gcc5-Os.o
69210504    3170616       0 72381120    45072c0 testcase.tcc.o

【讨论】:

    【解决方案3】:

    http://tinycc.org 试用 Fabrice Bellard 的微型 C 编译器 tcc

    chqrlie$ time tcc -c test4.c
    
    real    0m1.336s
    user    0m1.248s
    sys     0m0.084s
    
    chqrlie$ size test4.o
       text    data     bss     dec     hex filename
    38953877        3170632       0 42124509        282c4dd test4.o
    

    是的,这在一台非常基本的 PC 上是 1.336 秒

    当然我无法测试生成的可执行文件,但目标文件应该可以与您的程序和库的其余部分链接。

    对于这个测试,我使用了文件mex.h的虚拟版本:

    typedef struct mxArray mxArray;
    double *mxGetPr(const mxArray*);
    enum { mxREAL = 0 };
    mxArray *mxCreateDoubleMatrix(int nx, int ny, int type);
    

    gcc还没有编译完……

    编辑:gcc 严重占用了我的 Linux 机器,我无法再连接到它:(

    【讨论】:

    • 如果我可以问一个快速的后续问题:既然您似乎对 tcc 有经验,那么您知道添加外部 mex.h 文件的正确语法吗?我阅读了文档并尝试了: 'tcc test4.c -Idir"/usr/local/MATLAB/R2015a/extern/include" -Idir"/usr/local/MATLAB/R2015a/simulink/include"' 但不幸的是,这返回了“ test4.c:1: error: include file 'mex.h' not found" 即使它在那个目录中。
    • 删除选项中的dirtcc test4.c -I"/usr/local/MATLAB/R2015a/extern/include" -I"/usr/local/MATLAB/R2015a/simulink/include"。选项与gcc的相同。
    • @user650261 tcc 的命令行语法与 gcc 基本相同。
    猜你喜欢
    • 2012-06-13
    • 2010-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多