Slide 1

Slide 1 text

1 22 デバッグの話 慶應義塾大学理工学部物理情報工学科 渡辺宙志 2024/10/10 研究室ミーティング 主に数値計算屋向けの

Slide 2

Slide 2 text

2 22 注意 この資料は一人で小規模なコードを開発する人向け、すなわ ち自分で入れたバグを自分でデバッグするためのものです。 数値計算屋みたいな

Slide 3

Slide 3 text

3 22 典型的な研究スパン 年に二編論文を書く → 半年で一つの研究が完結 プログラム開発+計算 執筆 調査 • 調査:先行研究の調査や、計算手法についての調査 • 開発+計算:プログラム開発、計算の実行 • 執筆:結果の解析+論文執筆+投稿 しかし実態は・・・ 執筆 調査 デバッグ 開発 • 開発時間の大部分はデバッグに費やされている • 初心者であるほど、デバッグの占める割合が長くなる • コードの高速化は、研究時間の短縮にさほど寄与しない 計算

Slide 4

Slide 4 text

4 22 デバッグは進捗ではない 開発 デバッグ 開発 デバッグ 計算 解析 開発 デバッグ 計算 解析 解析 計算 Aさん Bさん デバッグは時間がかかり、集中力が要求され、達成感もある しかし、結局は自分で入れたバグを自分で取る作業(マッチポンプ) 作業している時間が長いのはAさんだが、進捗が出ているのはBさん 「デバッグは進捗ではない」ということを肝に銘じること

Slide 5

Slide 5 text

5 22 デバッグの基本哲学 「ここまでは大丈夫」という砦を築きながら進む 頭を使わない なるべく機械的にチェックできる仕組みを作る 頭を使うのは最後の最後だけ 安全地帯を作る

Slide 6

Slide 6 text

6 22 デバッグの基本哲学 重要な書類をなくしてしまった 手当たり次第に探す 「ここには無い」という場所を広げる

Slide 7

Slide 7 text

7 22 頭を使わない 調べた書類の束を部屋の中においておくと・・・ チェック済み チェック済み 未チェック 「どの束を調べたか」を覚えておかなくてはいけない →デバッグ時は頭を使ってはならない

Slide 8

Slide 8 text

8 22 安全地帯を作る チェック済みの書類は部屋の外に出す まだ調べていない チェック済み 砦 確実にこっち側には無い あるとすればこっち側 デバッグは「ここまでは大丈夫」という領域を広げる作業 作業が進むほど「ここは大丈夫」という安全地帯が増える 何かを記憶する必要がない(単純作業になる)

Slide 9

Slide 9 text

9 22 デバッグで最も重要なこと バグを素早く修正すること バグの原因を究明すること そもそもバグを入れないこと バグを入れないコーディング バグを発見した時の対処法

Slide 10

Slide 10 text

10 22 コードを書く際の注意 いきなりコード全体を書いて動 作確認してはならない ※特にAIを使う人は注意

Slide 11

Slide 11 text

11 22 例:分子動力学法 1. 相互作用がない粒子の時間発展を調べる 周期境界条件の確認 自由境界条件(反射壁)の確認 2. 相互作用のある二粒子の衝突を調べる エネルギーが保存することを確認 (カットオフの実装が正しいか?) 運動量が保存することを確認 軌道が正しいか確認

Slide 12

Slide 12 text

12 22 例:分子動力学法 3. 相互作用のある二粒子の衝突をまだ調べる 周期境界をまたいだ衝突は大丈夫か? 斜め方向に衝突した時の軌道は 想定通りか? 以上のチェックにより以下が「安全地帯」となる • 境界条件の扱い • 力の計算 • エネルギーの計算 (特にカットオフまわり) 多粒子系にしてバグったら、追加で実装した箇所を疑う

Slide 13

Slide 13 text

13 22 例:モンテカルロ法 二次元ポッツ模型を書けと言われた →初手で二次元ポッツ模型を書きはじめない 1. スピンが2つしかないイジング模型を書く 状態が4つしかないので、厳密解と比較可能 エネルギー、磁化などの温度依存性が厳密解と一致することを確認する (スピンフリップの実装が正しいことを確認) 2. 一次元イジング模型を書く エネルギー、磁化などの温度依存性が厳密解と一致することを確認する ここまで確認後、一次元ポッツ、二次元イジング、二次元ポッツ、へと進む バグが入ったら、厳密解と一致したところからの差分だけをチェック

Slide 14

Slide 14 text

14 22 バグを見つけた時 いきなりコードを修正してはならない バグの原因を特定しないままコードを修正し、正しいと思える振る舞い になったとしても、潜在的にバグが残っていてあとで困ることが多い

Slide 15

Slide 15 text

15 22 バグを見つけたら? ある程度開発が進んだコードでバグを見つけた いきなりデバッグをはじめない A B C デバッグにおいて最重要なのは原因究明 「いつのまにかなおっていた」は一番まずい →最初にやることは現場保全

Slide 16

Slide 16 text

16 22 バグを見つけたら? まず再現性の確認 • 本当にバグってる? • どの環境で、どのインプットファイルを使い、どんな実行方 法で、どんなバグが出たかを確定させる • ビルドミスはないか?正しいインプットファイルを渡してい るか?実行方法は正しいか? 現場保全 • デバッグ用ブランチを作成し、バグが発生するソースを保存 • 研究日記(←書いてるはず)に、ブランチ名と発生条件を記録 安全地帯の確保 • 「ここまではバグっていなかったはず」まで戻り、動作を確認 • 一番最新の「バグっていないコード」を安全地帯として確保する ここまで動作確認と単純作業しかしていない デバッグはなるべく頭を使わない

Slide 17

Slide 17 text

17 22 デバッグの基本は比較 できる限り現時点に近い安全地帯を確保し、容疑者を限定してから デバッグを開始する バグっていなかったコード(安全地帯) バグったコード 安全地帯からの修正箇所 バグが混入したとしたら、ここ

Slide 18

Slide 18 text

18 22 例:LAMMPS 何か複雑な初期状態や境界条件(外力など)を加えたインプット ファイルが動作しない ファイルを修正しながら期待する 動作になるまで何度も実行する 確実に動作するファイルを探し、動 作しないファイルとの差を調べる

Slide 19

Slide 19 text

19 22 二分探索 コードを実行したらSegmentation Faultと言われて止まった やってはならないこと ソースを見ながら原因を探してはならない 特に頭の中でトレース実行するのはダメ デバッグはなるべく頭を使わない やるべきこと まず、どこで止まったかを調べる → print文による二分探索 void func() { printf("1¥n"); // 何か処理 printf("2¥n"); // 何か処理 printf("3¥n"); } 出力が「1」ならこの間で止まっている 出力が「12」ならこの間で止まっている 場所を限定してから原因を考える

Slide 20

Slide 20 text

20 22 二分探索 コードを実行したら実行直後に死ぬようになってしまった 実行直後に死ぬのでprint文デバッグが使えない →こういう時も二分探索 int main(){ hoge(); fuga(); piyo(); hogehoge(); } こいつが死んだ int main(){ /* hoge(); fuga(); piyo(); hogehoge(); */ } すべてコメントアウト して動作確認※ int main(){ hoge(); fuga(); /* piyo(); hogehoge(); */ } 後半だけコメントアウ トして動作確認 ※「ここは絶対大丈夫」というところまで一度戻るのが大事 • ビルドに失敗しているのに気づかずに古い実行バイナリを見ていた • 間違ったライブラリをリンクしていた • そもそも実行方法を間違えていた 「ここは絶対大丈夫」がわり と大丈夫でなかったりする

Slide 21

Slide 21 text

21 22 AIとの付き合い方 ◯◯モデルのシミュ レーションをするコー ドを生成してください 生成されたコード 問題が起きた時に比較対象が存在しない→コードすべてを調べる必要がある 他人が書いたコードをデバッグするのは極めて困難(自分のも大変なのに) いきなりコード全体を生成させてはならない AIの使用例 • 関数単位で生成し、その度にテストする • エラーメッセージからバグの原因を推定させる • モデルの数式をコード化させる • 自分のコードをチェックさせる • etc.

Slide 22

Slide 22 text

22 22 まとめ デバッグでは、まず安全地帯を確保する 「ここまでは絶対大丈夫」を確保する 大丈夫なコードとダメなコードを比較するこ とでバグがある場所を限定する デバッグでは、頭を使わない print文やコメントによる二分探索など、やる べきことを単純作業に落とす デバッガやIDEの利用を覚えるのは、上記の基礎ができてからの方が良いと思う