はじめに
C++でアスタリスクをつけすぎると端末が落ちるという記事を書いたら、@tanakhさんから
ソースコードのサイズには線形なんじゃなかろうか / 他5コメント https://t.co/Rm9c0ceWpa “C++でアスタリスクをつけすぎると端末が落ちる - Qiita” https://t.co/JrrLZu8WT8
— Hideyuki Tanaka (@tanakh) 2018年2月23日
というコメントがありまして。
なるほど確かにソースコードのサイズはアスタリスクの数の二乗に比例するので、コンパイル時間とソースコードのサイズの関係は線形だなぁ、と。
ただ、内部で型のテーブルとか作ってると思うんで1、これが単純にソースコードのサイズの問題なのか調べるために、今度はもう少し簡単なコードを書いてみて、コンパイル時間やコンパイラが利用するメモリを調べてみようと思ったわけですよ。
今回のトリビアの種
こんなのを書きました。
def generate(n)
open("test.cpp","w") do |f|
f.puts <<EOS
#include <cstdio>
int
main(void){
int i=0;
EOS
n.times do
f.puts " i++;"
end
f.puts <<EOS
printf("%d\\n",i);
}
EOS
end
end
n = 100
n = ARGV[0].to_i if ARGV.size > 0
generate(n)
これは、単にi++;
を並べるだけのコードです。
$ ruby gen.rb 5
とかすると、
#include <cstdio>
int
main(void){
int i=0;
i++;
i++;
i++;
i++;
i++;
printf("%d\n",i);
}
が出力されます。数字を増やすとi++;
の行数が増えていきます。これを、以前の記事と同じくらいのサイズになるように調整して、コンパイラの負荷を調べてみようと思ったわけです。
まず10万行くらいから行きますかね。
$ ruby gen.rb 100000
$ wc test.cpp
100006 100008 700067 test.cpp
$ gtime -f "%e %M" g++ test.cpp
2.53 205680
楽勝ですね。じゃ、50万行。
$ ruby gen.rb 500000
$ gtime -f "%e %M" g++ test.cpp
g++: internal compiler error: Segmentation fault: 11 (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://github.com/Homebrew/homebrew-core/issues> for instructions.
Command exited with non-zero status 4
3.47 515904
・・・ん?なんかメモリを使い切る前に変な死に方したぞ。
「10万行では死なず、50万行では死にました。ということは、どこかにぎりぎり死ぬ行数があるはずですね。これってトリビアになりませんかね。」
このトリビアの種、つまりこういうことになります。
「整数をXXXXXX回インクリメントするコードを食わせると、コンパイラが死ぬ」
実際に調べてみた。
調査コード
どこで死ぬかを二分探索するわけだが、面倒なのでスクリプトにやらせよう。手抜きだが、こんな感じのスクリプトになると思う。
def generate(n)
open("test.cpp","w") do |f|
f.puts <<EOS
#include <cstdio>
int
main(void){
int i=0;
EOS
n.times do
f.puts " i++;"
end
f.puts <<EOS
printf("%d\\n",i);
}
EOS
end
end
def search
s = 100000
e = 500000
while (e != s && e != s+1)
n = (e+s)/2
generate(n)
if system("g++ test.cpp 2> /dev/null")
puts "#{n} OK"
s = n
else
puts "#{n} NG"
e = n
end
end
end
search
単純に10万行から50万行の間を二分探索していくコード。コンパイルが成功したかはsystem
の返り値で判定している。
さっそく実行してみよう。実行環境は前回と同じでこんな感じ。
- 測定環境
- MacOS X High Sierra
- プロセッサ 3.3 GHz Intel Core i5
- メモリ 8GB
- g++ (Homebrew GCC 7.2.0) 7.2.0
$ ruby increments.rb
300000 OK
400000 OK
450000 NG
425000 NG
412500 OK
418750 OK
421875 NG
420312 NG
419531 NG
419140 OK
419335 OK
419433 NG
419384 NG
419359 OK
419371 OK
419377 OK
419380 NG
419378 NG
というわけで、419377回インクリメントするコードならコンパイルできて、419378回インクリメントするコードを食わすとg++が死ぬ。
- 他に調べたこと
- メモリが半分の4GBしか積んでないMac Book Proで試しても同じ結果になったので、メモリの問題ではない。
- Linuxでは同じバージョンのGCCでもエラーにならない(追記:より大きなサイズではUbuntuでも死んだそうです)
- インテルコンパイラも大丈夫
- clang++ではMacでもエラーにならない
Windowsは調べてない。
まとめ
こうしてこの世界にまた一つ
新たなトリビアが生まれた2。
整数を419378回3インクリメントするコードを食わせると、g++ (Homebrew GCC 7.2.0)が死ぬ。