筆者の個人的な経験によるデバッグの仕方 [最近やらかしたバグ、失敗][Top]

目次
具体例
デバッグの仕方
標準となるプログラムを用意しておくこと
考えが煮詰まったらしばらくうっちゃっておく
その他、デバッグのための(経験的な)常套手段
常套手段(まとめ)
どんな時でもデバッグは大変である
予想されるバグの例
並列化作業でのバグの例(parallel.htmlページへ)
(関連文献)筆者が2009年10月から2010年9月まで雑誌 「金属」で連載した[記事 ]の第九回参照。

具体例

ここでは各論(各サブルーチンの説明)に入る前に、最近やらかしたバグの 話しをしてみたいと思います。バグがあったのはこのルーチンです。

このルーチンは希土類計算を目的として、非局所f部分に対応するルーチン をs、p、d非局所に対応していた元のプログラムに付加したもので、この改 良過程で生じたバグです。全エネルギーの計算結果とストレスの計算結果が一 致しないとうい問題が生じました。十分にエネルギーカットオフを取れば、全 エネルギーの最小なところでストレスの値はゼロになるはずですが、実際計算 してみると、全エネルギー最小なところで、ストレスの値が有限で大きな値を もってしまいました。

このルーチンの問題を以下に説明します。文番号1400の変数Lに関する ループの外側で、CWLなる配列に値を代入しています。ここで、CWL(1 0)は扱う原子がs、p、d非局所かs、p、d、f非局所かで値が異なりま す。それを次のループ(文番号1922、1923)の内側のIF文で条件設 定しています。

実はこれが間違いで文番号1923のループは個々の原子に関するループで、 このループ内ではZFCなる配列変数の初期化を行なっているだけです。そし てCWLが実際に使用されるのは次の文番号1420、1410のループです。 ところが、CWLの値は前の文番号1923のループで定義されています。

扱う系が原子1種類だけの場合は問題ないのですが、s、p、d非局所な原 子とs、p、d、f非局所な原子からなる系を扱う場合には正しい計算を行な わなくなります。最初、Eu(bcc構造)の計算を行なった時は、全エネルギー とストレスの値の関係は正しいものでした。ところがEuS(岩塩構造)では正 しい関係にならず、先に述べたような不一致が出てきました。これは、このルー チンのままだと、最初の原子(Eu、希土類、CWL(10)=3.0D0)の段階で、次の 原子Sの設定(CWL(10)=1.0D0)が行なわれてしまっています。

このためs、p、d非局所とs、p、d、f非局所の2種類以上の原子から なる系を扱う場合はバグが存在することがわかります。ここでバグを修正した 正しいルーチンを示します
この修正された(必ずしも速度的なチューンアップや、プログラムの構造的 な正しさ美しさを考慮していません。)ルーチンをみればわかるように、CW L(10)のIF文による条件別設定を文番号1410のループ内で行なうよ うにしました。これでEuS岩塩構造でも全エネルギーとストレスの関係は正し く出るようになりました。(但し、希土類金属としての計算が正しくできるよ うになったわけではない。実は、希土類金属の擬ポテンシャルによる計算は大 変難しく、まだ検討しなければならないことが多数残っています。)

デバッグの仕方

バンド計算プログラムを作っていると、いろいろな局面でバグに遭遇し、何 日も何週間も、場合によっては何カ月も悩まなければならないことがあります。 ここでは、そのような場合(特に、何カ月もデバッグに費やさなければならな い時)での対処の仕方について述べていきたいと思います。

(1)標準となるプログラムを用意しておくこと
これは既存のプログラムを拡張、改良する場合には当然必要なことです。つ まり改良、拡張中にバグが見つかり、デバッグ中の場合、最低この段階まで戻 れば正しい結果を与えるオリジナルプログラムを用意しておくことが必要です。 このオリジナルプログラムが必ず正しい結果を与えるのならば、改良、拡張中 のプログラムの実行結果のチェックがいつでも可能で、万が一、不一致が生じ てもプログラムの変更箇所が容易にわかり、デバッグの助けになります。

(この時、改良、拡張は一気に行なうのではなく、適当な段階毎にコンパイル、 実行し常に計算結果のチェックを行ないながら仕事を進めていくのが良いかと 思います。こうしておけば、結果の不一致があっても、最初のオリジナルでは なく直前まで正しく動いていたバージョンと比較できるので、変更箇所の量が オリジナルと比べてずっと少なくて済み、間違っている部分を発見することが 非常に楽になります。〔その変わりバージョン管理をしっかりやらねばなりま せん。〕)

もし正しい結果を与えるオリジナルプログラムがない場合、実行結果が明ら かにおかしかったりする場合はまだいいのですが、どうも微妙にとか、何とな くおかいし場合に、よって頼るべきもの(つまり、この段階での計算なら正し いと自信が持てるもの)がないので、デバッグが非常に困難なものとなります。

(2)考えが煮詰まったらしばらくうっちゃっておく
デバッグというものは非常に困難な作業であり、プログラムのコーディング は、数日、数週間でできても、デバッグは何カ月もかかってしまうことがあり ます。何カ月もかけてバグがとれれば良いのですが、場合によってはにっちも さっちもいかなくなることもあります。ほとほと考えが煮詰まってしまって万 策尽き、もし、他にも重要な仕事があったり、逆にそれほど時間的に切迫して いなければ、思い切ってデバッグ作業を一時中断し、別の仕事を行なうか、家 に帰って寝てしまうかして、気分転換を計るという手があります。
これはあくまで、経験的な話しですが、2週間とか1カ月ほどデバッグ作業 をしないでいて、久しぶりに問題のプログラムを眺めていると、ふとバグに気 付くことがあります(必ず気付くわけではないし、何の根拠もない)。

他にも、”間際効果”というものがあり、学会発表や、D論の発表が近付く と、不思議とバグが見つかり、一気に仕事が進むことがあります。(この間際 効果なるものにも、その存在を証明する根拠はどこにもないです。)

(3)その他、デバッグのための(経験的な)常套手段
いくつかありますが、思いつくまま以下に挙げていきます。

(イ)取り敢えずデバッガーを使ってみる
対象となる計算規模がさして大きくなく、エラーもすぐに出てくる場合は、 この方法は非常に有効であると思われます。但し、デバッガーの使い方を熟知 していることが必須です。また計算規模が大きい場合、デバッガーが動かない 場合があります。更に、コンパイラーの種類にもよりますが、UNIX系のコンパ イラではデバッグオプション(普通 -g)を付けると最適化を行ないません (最適化モード用のデバッグオプションのあるコンパイラ〔例DEC-FORTRAN〕 もあるにはあります。)。従って、エラーの出てくる箇所まで到達するのに大 変な時間が掛かってしまうことがあります。

従って、どのような場合にもデバッガーが万能であるとは言えません。因み に筆者はめったにデバッガーは使いません(使い方も良くわかっていないのも 理由の一つ)。

(ロ)計算の途中結果を出力する
これは、一番確実で正統的な方法かもしれません。とにかく、エラー箇所に 到達するまで、各ルーチン毎に計算結果を片っ端から出力するという手です。 この場合、前述の確実に正しい結果を与えるプログラムによる途中結果との比 較ができればエラー箇所を見つけることは比較的容易な場合があります。但し、 気を付けないと、大規模な繰り返しDOループ中に下手にWRITE文を書い ておくと、大変な量の出力が生じます。もしこれをファイルに書き込むように しておくと、たちまちディスクが溢れてしまい、システムが止まってしまう可 能性すらあります。またバッチジョブ形式でジョブを投入してると、例え6番 に書き出して(WRITE (6,100) A(N)等)いても、ジョブ用のバッファが溢れて しまいジョブが異常終了してしまいます(場合によっては、これもシステムを 止める原因になります)。

計算途中のデータを書き出すときは、くれぐれもこのようなことにならない ように細心の注意(特に所属組織の共通〔共有〕の計算機システムを使ってい る時)が必要です。データの出力は何も全部出す必要がなく、例えば大きなD Oループ内の変数の場合、可能ならその和をとり、それ同士を比較するという 手があります。

(ハ)コンパイル時のオプションを変えてみる
コンパイラの最適化が厳し過ぎるため、正しくない結果を与えることがあり ます。途中で異常終了する、計算結果が明らかにおかしい場合に、コンパイラー の最適化レベルを下げて再度計算してみると正しい結果を与えることがありま す。この場合、コンパイラーのバグなのか、プログラム側のバグなのか判然と しない場合もありますが、他機種では最適化しても正しい結果を与える場合は そのコンパイラーのバグを疑ってみる必要があります。

このようなコンパイラーのバグと思われる場合、エラーの起こる箇所付近に WRITE文(時にはコメントなど)を挿入するだけで正しく動いてしまうこ とがあります。このような場合、プログラム上の問題が残っている場合もあり 得ますが、コンパイラーのバグも疑ってよいかと思います。このような時には、 コンパイラーの製造元(普通、大型機やワークステーションの世界ではコンパ イラーとマシンの製造元は一致します)になるべく早く問題を指摘することを 勧めます。相手側が適確に動いてくれるかどうかは当該するメーカーに強く依 存します。

ただ、この手のコンパイラーのバグに引っかかるプログラムは、プログラム 側にも何らかの問題(プログラムの内容に非常にトリッキーな部分がある、例 えばサブルーチンの呼び出す側と呼び出される側の引数〔変数〕の型を〔高速 化のため〕わざと一致させない場合があります。本当はやってはいけないこと ですが、それでも大抵コンパイラーは通ってしまい、正常な結果を与えてしま います。)を抱えている場合が多くあります。一概にコンパイラーだけを悪者 にすることは(もっと重大な問題を見落す可能性があり)大変危険なことにな ります。

(ニ)異なるシステム同士で比較する
異なる計算機上で、問題のプログラムを動かし、エラーの出方を比較すると いう手があります。これはどのような場合でもうまく行くというわけではあり ません。かなり特殊な状況の場合と思ってください。

デバッグの場合、時にはエラーの箇所が判然とせず、エラーの原因も皆目見 当もつかない場合があります。この時、異なるシステムで問題のプログラムを 動かし、計算途中の結果やエラーメッセージの違いからエラーの原因を見つけ 出せることが(たまに)あります。ただし、これは互いのシステムをある程度 以上熟知しており、この手の計算やデバッグの経験が相当ないとこの手の手法 は使えないと思います。

(ホ)誰かに相談する。または誰かとエラーについて議論してみる
意外とバグというのは、担当している本人には気付きにくいものですが、他 人がみるとすぐに指摘できてしまう時があります。また、共同研究者や、同じ 研究室の同僚(テーマは違っても大抵バンド屋さんと思われます)とプログラ ム上の問題について話し合っている内に、ふと解決策が頭に浮かんでくること があります。場合によっては、全くの門外漢の人との議論からエラーの原因が わかることもあります(あるでしょう)。

(ヘ)バグも仕様の内(「マーフィーの法則」アーサー・ブロック著、アスキー 出版局刊より)としてしまう
これは大変危険な解決策です。明らかに、バグが致命的でなく、計算上もさ して重要な影響を与えることはないと判断できるなら、バグをそのままにして おくという手もあります。特に、単なる出力フォーマット上の問題のような、 明らかに計算そのものに全く影響を与えないようなバグは、一時無視してほうっ ておくことも可能です。

但し、バグが計算に関わっている場合は、安易にそのままにしておくことは 決して勧められません。余程その計算に熟知し、この手のバンド計算に熟練し ていなければ、鷹をくくることは非常に危険です。今、問題がなくても後々大 問題になる可能性が大きいです。

これからバンド計算に手を出そうとする人は、バグがあれば徹底的に原因を つきとめ、問題を解決するような習慣を最初からつけておくことを強く勧めま す。


以上を簡単にまとめると
1、正しい結果を与える標準となるべきプログラムを必ず用意する。
2、取り敢えずデバッガーを使ってみる。(アナライザもあれば役立つかも)
3、他に迷惑をかけない範囲で途中結果を出力する。(1、と比較)
4、計算条件(系のサイズや原子の種類など)を変えてみる。
5、コンパイラーのオプションを変えて実行(例、最適化のレベルを下げる)。
6、異なるシステム(OS、機種など)同士で実行、比較してみる。
7、デバッグ作業の履歴をきちんと記録しておく。
8、デバッグに伴うプログラムの変更箇所には必ず注釈をつける。
9、バグが見つからない時は、いろいろな方法を試してみる。
10、信頼できる人々(1人だけでなくてよい)に相談(議論)する。
11、バグは大抵単純(時に見つけ難い)で深刻な影響を与えるものがほとんど。
12、先入観(思い込み)を棄てる。(虚心坦懐に)
13、デバッグ作業を面倒がらない。(急がば回れ)
14、可能な限り妥協してはいけません。(結局問題を先送りするだけ。)
15、いよいよ煮詰まったら、しばらくほうっておいてみる。(気分転換)
16、末期段階、バグも仕様の内と開きなおる(本当は絶対やっちゃいけない)。

やはり、16に関して、明らかに間違っている計算結果をもってして、研究 を進めて(間違いを承知していながら、それを正しいデータと偽って研究発表 等を行なうこと)いくことは研究者の態度としては論外といえるものです。良 い研究者はこのようなことをしてはいけません。

(4)どんな時でもデバッグは大変である
特に、バグの中で問題なのは、何が何だか分らないバグ(原因不明で止まり、 どうして止まるか分らない場合、系のサイズを大きくしたりと、ある条件にす ると原因不明で止まる場合など)、正しい計算結果と比べて微妙(無視できな い程度)に値が異なるバグ(これが一番厄介だと私は思っています。)などで す。

何がなんだか分らないバグの場合、先に述べた(イ)から(ホ)までの手段 や1から15までの事柄を踏まえて、とにかく原因を突き止めることです。む しろ途中で止まるバグや、計算結果が全くでたらめ(大き過ぎたり、小さ過ぎ る)な場合は、ある程度以上の経験を積むと対処できるようになっていきます。 それまでは、いろいろなデバッグ手段をためし、いろいろな条件設定の下での 計算結果の比較検討を行ない、わからないことはマニュアルや参考文献を探し、 それでもわからない時は、良く知っていそうな人(指導教官、所属研究室の助 手、先輩、同僚などで頼りになりそうな人で、できればあまり忙しそうにして いない時)に質問するなり、助けを求めるなりして、経験を積んで貰うしかな いと思います。

むしろ、微妙に値が違う場合は、原因がはっきりしていれば対処できますが、 大抵こういうような局面では原因がさっぱり分らないことが多いです。但し、 この手のバグは大抵単純(故に見つけ難い)ながら、計算結果に深刻な影響を 与えている場合が多いです。
(予想されるバグの例)これらの例をいくつか 挙げると、

  1. 単純な係数の間違い。(例4*を2*とした、や単位系の間違いなど)
  2. 計算すべき式が、もともと間違っていた。(論文の式は鵜飲みするな)
  3. (配列)変数等の初期化の間違い。(例、ゼロにしたつもりでそうなっていない。)
  4. (配列)変数の定義の間違い。(例、型の不一致)
  5. 配列変数で、定義した外側の領域にアクセスしようとした。
  6. サブルーチンへの(配列)変数の受け渡しの間違い。(変数の数や型の不一致)
  7. (配列)変数名のスペルミス。
  8. 入力データの間違い。
  9. 計算制御変数の間違い。(例、時間刻み幅のミスや結晶対称性の間違い)
  10. その他
などなどです。

特に、サブルーチンで受け渡す側と受け取る側の変数の数や型が合わなかっ たり、変数名のスペルミスはコンパイラーがエラーを出さない、出せない場合 があります。また入力データの間違いや、計算条件の設定ミスもコンパイル時 にはエラーが出ません。(サブルーチンの変数受け渡しの問題は、コンパイラー の種類によって、標準ではエラーの出ないものが多いですが、大抵はオプショ ンでエラーを出せるようにはなっています。オプションについてはmanコマン ドか、マニュアルを参照してください。)

配列変数で、定義した外側の領域にアクセスしたというのは、つまり配列A がA(1000)と定義してあったとして、1001番目以降のAについて演 算、代入等の操作をしようとしたことを意味しています。(動的なメモリーの 割付けが行なわれ、それに配列Aが対応するようにできていれば〔本当にでき るかは、いまのところ私にはわかりません。〕問題ないかもしれません。)

この場合、コンパイルの時点ではエラーは出ない場合がほとんどです。大抵 は実行時に記憶領域を壊したとか、セグメントが壊れたとか、記憶領域障害と かエンドユーザーにはさっぱりわけの分らないエラーメッセージが出て、プロ グラムが止まってしまいます。これは、定義した以外の配列の領域を使用した ため、メモリー(記憶領域)上のデータがおかしくなってしまうためで、この 場合、エラーも配列の操作を間違えたところとは全く関係ないところで止まっ てしまいます。このため、エラー箇所が見つけ難くなっています。

このエラーを見つけるためには、デバッガーを使うことが有効かもしれませ ん(コンパイル時にデバッグオプションが必要、またデバッガーの操作も熟知 していることが必須)。またコンパイルオプション(詳細は各システムのマニュ アルを調べるか、そのシステム〔UNIXを想定〕上でman f77としてみる。)で 実行時の配列領域違反している部分を指摘するようにさせることができるもの があります(大抵できると思います)。ただ、この方法はこのような場合には 全く無力であることも多く、非常にデバッグに苦労することがあります。

この逆の場合として、考えていた数より、ずと少ない数の配列しか使ってい ないバグもあります。”やさしいバンド計算プログラムの作り方”のところで、 変数、配列定義用のインクルード文PACVPPの説明をしたかと思います。 そこに、KNGとKNG1という変数が定義されています。この変数の大小関係は必 ずKNG > KNG1です(大体KNG=8*KNG1)。

以前、計算ルーチン内のDOループ変数で、KNGとすべきところをKNG1とし て計算していたことがありました。一文字違うだけですが、このバグで大変苦 労しました。KNG > KNG1で、関係する配列等はちゃんとKNGまで定義してあっ たので、記憶領域障害等のエラーもでず、計算そのものも何のエラーも示さず、 正常に終了していました。しかし、どうも計算結果が微妙に(表現が難しい) おかしいと思いました。当初は計算機誤差だろうと鷹をくくっていたのですが、 どうも気になって、いろいろなテスト計算をしていく内に、何らかのバグと考 えるようになりました。しかし、このバグの追求は困難を極め、バグ取りを試 みて、疲れてほうっておいて、またバグ取りを試み、また疲れて他の仕事をす るというのを繰り返して、何カ月もかかって、プログラムリストを何度目かの 点検をしている時に、ふとDO文の制御変数が1からKNGではなく、KNG1になっ ていて、実際このループではKNGではないかと気付いて、バグがようやく取れ た経験があります。バグとしてはたった一文字の違いで、非常に単純でしたが、 非常にデバッグに苦労した例です。

また並列化作業の上で犯したバグの が”FACOM VPP300、500での並列化”編にあります。

[先頭][総目次 ][最初に戻る][FACOM VPP300、500での並列化 ][最近やらかしたバグ、失敗]
[大阪弁]