整数を419378回インクリメントするとMacのg++が死ぬ

はじめに

C++でアスタリスクをつけすぎると端末が落ちるという記事を書いたら、@tanakhさんから

というコメントがありまして。

なるほど確かにソースコードのサイズはアスタリスクの数の二乗に比例するので、コンパイル時間とソースコードのサイズの関係は線形だなぁ、と。

ただ、内部で型のテーブルとか作ってると思うんで1、これが単純にソースコードのサイズの問題なのか調べるために、今度はもう少し簡単なコードを書いてみて、コンパイル時間やコンパイラが利用するメモリを調べてみようと思ったわけですよ。

今回のトリビアの種

こんなのを書きました。

gen.rb
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 

とかすると、

test.cpp
#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回インクリメントするコードを食わせると、コンパイラが死ぬ」

実際に調べてみた。

調査コード

どこで死ぬかを二分探索するわけだが、面倒なのでスクリプトにやらせよう。手抜きだが、こんな感じのスクリプトになると思う。

increments.rb
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)が死ぬ。


  1. いや、コンパイラ屋さんじゃないのでよく知らんけど。 

  2. えーと、もともとはソースコードサイズとコンパイル時間の関係を調べようかと思ってたんだっけ。まぁいいや。気になったら誰かやって。 

  3. しかし419378って数字、中途半端だな。なんで決まってるんだろ? 

27contribution

うちの環境でも試して見た所698977回で死にました。

750000 NG
625000 OK
(中略)
698976 OK
698978 NG
698977 NG
  • Ubuntu 16.04.3 LTS
  • Intel Core i7 CPU 860 @ 2.80GHz
  • メモリ 12GB
  • g++ (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
3292contribution

@Toru3 さん
情報ありがとうございます。サイズを大きくしたらUbuntuでも死ぬのですね。やはりメモリの問題でしょうか……

いずれにせよ「Mac限定」とタイトルにあったのは誤りだったので修正しました。

118contribution

当方の環境(Linux Mint)でも、698910回前後(何回か測定してみましたが、結果は多少前後するようです)で死にました。

当方の環境

  • OS: Linux Mint 18.2 x64 (Ubuntu 16.04 base)
  • コンパイラ: g++ version 7.2.0 (Ubuntu 7.2.0-8ubuntu3.1~16.04.york0)
  • CPU: Intel Core i7-7820X (SpeedStep ON, Turbo Boost ON)
  • メモリ: 32GB
118contribution

続けてのコメントの投稿、失礼いたします。

上記とは別に、Windows Subsystem for Linux上のUbuntu 16.04(コンパイラ: g++ version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9))でも試してみたところ、今度は268837回で死にました(興味深いことにこの環境では、何度計測しても、結果の変動はありませんでした)。
なお、CPUとメモリは上記のLinux Mint環境のものと全く同じです。

This comment has been deleted for violation of our Terms of Service.
60contribution

コンパイルオプションで結果に違いが出るものがあれば、落ちるのはそのオプションに関連する理由だと推測できるかも。
コードが単純なので無関係なオプションの方が多そうですが、「最適化一切なし」とかにすると限界は高くなるかもしれませんね。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.