GCCで大きな配列を初期化するコードを-gつきでコンパイルすると遅い
GCCで大きな配列を初期化するコードを-gつきでコンパイルすると遅い
はじめに
配列の初期化について調べてたら変な現象を見つけたのでメモ。
現象
以下の二種類のコード、test.cpp、test2.cppを考える。
#include <stdio.h>
const int N = 100000000;
int
main(void){
static int a[N] = {};
printf("%d\n",a[0]);
}
#include <stdio.h>
const int N = 100000000;
int a[N] = {};
int
main(void){
printf("%d\n",a[0]);
}
test.cppでかい配列をmain関数内でstatic宣言+初期化。test2.cppでかい配列をmain関数の外で宣言+初期化。
これをg++ -O3 -g test.cpp/test2.cppと、最適化オプション+デバッグ情報付きでコンパイルするとえらく時間がかかったりかからなかったりする。この現象は環境(おそらくGCCのバージョン)に依存する。
MacやCygwinで試した場合
まず、Mac+GCCの場合。環境はiMac Late 2015で、石は3.3 GHz Intel Core i5である。
$ g++ --version
g++ (Homebrew GCC 6.3.0_1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ time g++ -O3 -g test.cpp
g++ -O3 -g test.cpp 188.53s user 41.20s system 92% cpu 4:08.32 total
たった数行(実質一行)のコードのコンパイルに、なんと4分もかかる。
clang++はすぐに終わる。
$ clang++ --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ time clang++ -O3 -g test.cpp
clang++ -O3 -g test.cpp 0.04s user 0.02s system 85% cpu 0.064 total
デバッグオプションをつけなかったり、最適化オプションを消しても早く終る。
$ time g++ -g test.cpp # 最適化オプションなし
g++ -g test.cpp 0.53s user 0.03s system 97% cpu 0.570 total
$ time g++ -O3 test.cpp # デバッグオプションを消した
g++ -O3 test.cpp 0.54s user 0.02s system 97% cpu 0.575 total
main関数内にある配列宣言を外に出した奴(test2.cpp)を食わすとコンパイルはすぐ終わる。
$ time g++ -O3 -g test2.cpp
g++ -O3 -g test2.cpp 0.53s user 0.03s system 97% cpu 0.568 total
Cygwinでも同様な振る舞い、つまり関数内に宣言したtest.cppは遅く、関数の外に出したtest2.cppは早い、という現象が起きる。
$ g++ --version
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ time g++ -O3 -g test.cpp
g++ -O3 -g test.cpp 382.31s user 6.91s system 81% cpu 8:00.41 total
$ time g++ -O3 -g test2.cpp
g++ -O3 -g test2.cpp 1.28s user 0.23s system 97% cpu 1.552 total
たった数行のファイルに、コンパイル時間8分!
Linuxの場合
Linux + g++ (GCC) 5.1.0とかでも2分くらいかかる。
$ g++ --version
g++ (GCC) 5.1.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ time g++ -O3 -g test.cpp
g++ -O3 -g test.cpp 116.28s user 4.30s system 99% cpu 2:00.82 total
また、Linuxの場合は配列宣言を関数の外に出しても時間かかる。
$ time g++ -O3 -g test2.cpp
g++ -O3 -g test2.cpp 115.31s user 4.87s system 96% cpu 2:04.01 total
手元に超古いGCC(4.1.2)があったので試すと、どちらのケースでもさほど時間がかからない。
$ g++ --version
g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-55)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ time g++ -O3 -g test.cpp
g++ -O3 -g test.cpp 0.23s user 2.01s system 59% cpu 3.762 total
$ time g++ -O3 -g test2.cpp
g++ -O3 -g test2.cpp 0.04s user 0.10s system 100% cpu 0.141 total
実行バイナリのファイルサイズ
コンパイルに時間がかかっている時、実行バイナリのサイズが大きくなる。
まず、Linux+GCCの場合、
$ du -h a.out
382M a.out
とファイルサイズが382MBになっている。
Linux+icpcだと短時間で終わり、バイナリも小さい。
$ time icpc -O3 -g test.cpp
icpc -O3 -g test.cpp 0.10s user 0.18s system 57% cpu 0.473 total
$ du -h a.out
512 a.out
Macの場合はa.out自体は大きくならないが、別の場所に保存されている情報が大きくなる。
$ du -h a.out
12K a.out
$ du -h a.out.dSYM/Contents/Resources/DWARF/a.out
381M a.out.dSYM/Contents/Resources/DWARF/a.out
ここまでだと、「時間がかかってる時にはバイナリサイズが大きくなる」と結論付けたくなるが、Linux+古いGCCでは、時間がかからず、かつバイナリもでかい。
$ time g++ -O3 -g test.cpp
g++ -O3 -g test.cpp 0.18s user 2.06s system 66% cpu 3.382 total
$ du -h a.out
382M a.out
ただし、-cでコンパイルしてもでかいオブジェクトファイルを吐くが、-Sつけると吐くasmは小さい。
$ g++ -O3 -g -c test.cpp
$ du -h test.o
382M test.o
$ g++ -O3 -g -S test.cpp
$ wc test.s
971 1902 13591 test.s
他のケースでは、-Sつけると馬鹿でかいasmを吐く。
まとめ
以上をまとめると、馬鹿でかい配列+初期化というソースをGCCでコンパイルした際、
- GCCのバージョンが古い(4.1.2)場合は、関数の中、外どちらで配列を初期化してもコンパイル時間はかからない
- GCCのバージョンが新しい(>5.4.0)場合は、関数の中でstatic宣言+初期化をすると時間かかるが、関数の外に出すと時間がかからない
- GCCのバージョンが中くらい(5.1.0とか)の場合は、関数の中、外どちらで配列を初期化してもコンパイル時間が長くなる
ということのようだ。最初、ファイルサイズ(=オブジェクトファイルサイズ)との関連を疑ったが、GCC 4.1.2の場合はそこそこコンパイルは早く、実行バイナリは大きい。ただし、時間がかかる場合は、g++ -O3 -g -Sで馬鹿でかいasmを吐く、というのは共通の模様。GCC 4.1.2の場合、実行バイナリは大きくなるが、吐くasmは小さい。
A Robot’s Sigh