COBOLバッチ処理の性能改善・最適化!劇的に処理を速くするベストプラクティス
生徒
「先生、COBOLで作ったバッチ処理が遅くて、夜中のうちに終わらなそうなんです。どうすれば速くなりますか?」
先生
「バッチ処理の時間が予定を過ぎてしまうことを突き抜けと言います。それを防ぐために性能改善が必要ですね。」
生徒
「性能改善って難しそうですが、初心者でもできるコツはありますか?」
先生
「もちろんです!データの読み方や計算の仕方を少し工夫するだけで、劇的にスピードアップするベストプラクティスがあるんですよ。」
1. バッチ処理の性能改善とは?
コンピュータのプログラムが動く速さを向上させることを性能改善や最適化(チューニング)と言います。特にCOBOLが得意とするバッチ処理は、膨大なデータを扱うため、たった1秒の遅れが何百万回と積み重なることで、数時間の差になって現れます。
例えば、1万枚の書類にハンコを押す作業を想像してください。一つ押すごとに立ち上がって道具を取りに行くのと、机に道具をすべて並べて座ったまま次々と押していくのでは、かかる時間が全く違いますよね。プログラムも同じで、無駄な動きを減らし、効率よくデータを取り扱うことが、高速化への第一歩となります。
2. ファイル入出力の回数を減らす工夫
プログラムの中で最も時間がかかる作業の一つが、ディスクにあるファイルを読み書きする操作です。これをI/O(アイオー)と呼びます。性能改善の鉄則は、このI/Oの回数を最小限にすることです。
データを一件読み込むたびにファイルを閉じたり開いたりするのは非常に非効率です。可能な限り、一度の読み込みで多くのデータをメモリ(作業領域)に取り込み、まとめて処理する仕組みを考えます。これを専門用語でブロック化と言います。身近な例で言えば、スーパーで卵を一つ買うたびにレジに並ぶのではなく、10個入りのパックを一度に買うようなものです。これにより、通信のオーバーヘッド(余計な待ち時間)を大幅に削減できます。
以下は、繰り返し処理の中で無駄な動きをしない、シンプルな構造の例です。
IDENTIFICATION DIVISION.
PROGRAM-ID. FAST-READ.
DATA DIVISION.
FILE SECTION.
FD IN-FILE.
01 IN-REC PIC X(100).
WORKING-STORAGE SECTION.
01 END-FLG PIC X(01) VALUE "N".
PROCEDURE DIVISION.
OPEN INPUT IN-FILE.
* 終わるまで一気に読み込むループ
PERFORM UNTIL END-FLG = "Y"
READ IN-FILE
AT END MOVE "Y" TO END-FLG
NOT AT END PERFORM MAIN-PROCESS
END-READ
END-PERFORM.
CLOSE IN-FILE.
STOP RUN.
MAIN-PROCESS.
* ここに処理を書く
CONTINUE.
3. 効率的なデータ型を選択する
COBOLでは数値の扱い方にいくつかの種類があります。その中でも計算速度に大きく関わるのが内部形式です。特に「COMP-3(内部十進数)」という形式は、コンピュータが計算しやすい形にデータを詰め込んでいるため、普通の文字形式で計算するよりも処理が速くなります。
画面に表示するための形式(外部十進数)のまま何万回も足し算を行うと、コンピュータはその都度「文字から数値へ」変換する作業を裏側で行います。これをあらかじめ計算用の型に定義しておくだけで、変換の手間が省け、計算時間を短縮できます。小さな工夫ですが、大量の数値を合算する集計処理などでは絶大な効果を発揮します。
4. 条件分岐の順番を最適化する
プログラムの中で「もし~なら」と判断するif文の書き方にもコツがあります。それは「もっとも発生確率が高い条件を一番最初に書く」というルールです。
例えば、100万件のデータのうち、9割が「東京都」で、残りが他の県だとします。このとき「大阪府か?」「北海道か?」と順番に聞いて最後に「東京都か?」と聞くのは効率が悪いです。最初に「東京都か?」と聞けば、9割のデータは一回の判断で終わります。判断の回数を減らすことは、CPU(頭脳)の疲れを減らし、スピードを上げることにつながります。
* 効率的な分岐の書き方
IF PREF-CODE = "13" THEN
* 東京都の処理(発生率が高いものを先に)
ADD 1 TO TOKYO-COUNT
ELSE
IF PREF-CODE = "27" THEN
ADD 1 TO OSAKA-COUNT
ELSE
ADD 1 TO OTHER-COUNT
END-IF
END-IF.
5. テーブル検索とバイナリサーチの活用
大量のデータの中から特定の情報を探す際、端から順番に見ていく方法(シーケンシャルサーチ)は、データが増えるほど時間がかかります。そこで、データをあらかじめ並べ替えておき、真ん中の値と比較して範囲を半分ずつ絞り込んでいくバイナリサーチ(二分探索)という手法を使います。
辞書で単語を引くとき、最初のページからめくる人はいないはずです。真ん中あたりを開いて、目的の単語が前か後ろかを確認しますよね。この賢い探し方をCOBOLのプログラムで実現する命令に「SEARCH ALL」があります。これを使うだけで、検索にかかる時間は驚くほど短縮されます。
6. 事前ソートによるマッチングの高速化
二つの大きなファイルを照らし合わせる「マッチング処理」を行う際、バラバラの順番のまま処理をするのは非常に困難で遅くなります。そこで、処理を開始する前にデータを特定のキー(社員番号や日付など)で並べ替えるソート(並べ替え)を行います。
両方のファイルが同じ順番に並んでいれば、プログラムは上から下へ一度流れるだけで、すべての照合作業を完了できます。これを「マージ」と呼びます。一度ソートする手間はかかりますが、全体で見れば圧倒的に速く終わる、バッチ処理の王道パターンです。
以下は、簡単な数値の比較で一致を確認するイメージです。
IDENTIFICATION DIVISION.
PROGRAM-ID. MATCH-CHECK.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 KEY-MASTER PIC 9(05) VALUE 10001.
01 KEY-TRAN PIC 9(05) VALUE 10001.
PROCEDURE DIVISION.
* 並んでいることを前提に比較する
IF KEY-MASTER = KEY-TRAN THEN
DISPLAY "一致しました。更新処理を行います。"
ELSE
DISPLAY "不一致です。スキップします。"
END-IF.
STOP RUN.
7. 無駄な初期化や重複処理を省く
ループ(繰り返し)処理の中に、一度だけ行えばよい作業が混じっていないか確認しましょう。例えば、円周率のような変わらない値を計算する処理がループの中にあると、100万回同じ計算を繰り返してしまいます。これは時間の無駄遣いです。
こうした不変の処理はループの外に出しておき、一度だけ計算して変数に保存しておきます。また、変数の初期化(値をゼロに戻すなど)も、本当に必要なタイミングで行っているかを見直します。パソコン初心者の方でも、「部屋を出入りするたびに大掃除をする」のが無駄であることは分かるはずです。必要なときだけ、最小限の掃除をすることが最適化のコツです。
8. データベースアクセスとコミットの頻度
ファイルを扱う代わりにデータベース(情報の倉庫)を使う場合、データの保存を確定させるコミットという操作のタイミングが重要です。一件処理するごとにコミットを行うと、その都度データベースとの通信や記録の書き込みが発生し、非常に遅くなります。
一方で、何十万件もまとめて最後に一度だけコミットしようとすると、今度はメモリや一時領域がパンクしてしまう恐れがあります。そこで「1000件ごとに確定させる」といった具合に、適度な間隔で処理を区切ります。この間隔を調整することを「コミット間隔のチューニング」と呼び、バッチ処理の安定と速度の両立には欠かせない作業です。
9. コンパイラの最適化オプションを使う
プログラムそのものを書き換えるのではなく、コンピュータが読み取れる形式に変換するソフト(コンパイラ)の設定を変えるだけで速くなることもあります。これを最適化オプションと言います。
このオプションを有効にすると、コンパイラが「この計算はもっと短くできるな」と自動的にプログラムを賢く書き換えてくれます。最近のコンパイラは非常に優秀なので、下手に人間が複雑なコードを書くよりも、シンプルに書いてオプションに任せる方が結果的に速くなることもあります。道具そのものの性能を引き出す、もっとも手軽な改善方法です。
10. 性能測定とボトルネックの特定
最後に、もっとも大切なことは「推測せずに計測する」ことです。どこが遅いのかを勘で判断するのではなく、実際にプログラムを動かして、どの部分に何秒かかったかを記録します。これをプロファイリングと呼びます。
意外な場所が時間を食っていることがよくあります。例えば、集計処理ではなく、実は画面に状況を表示する「DISPLAY」命令が一番重かった、というケースは珍しくありません。本番と同じようなデータを使ってテストを行い、数字を見て改善の優先順位を決めましょう。着実な測定こそが、最高のバッチ運用管理を実現する鍵となります。最後に、時間を計測する際のイメージコードを示します。
IDENTIFICATION DIVISION.
PROGRAM-ID. TIME-MONITOR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 START-TIME PIC 9(08).
01 END-TIME PIC 9(08).
PROCEDURE DIVISION.
ACCEPT START-TIME FROM TIME.
DISPLAY "処理開始時刻: " START-TIME.
* ここでメインのバッチ処理を実行
PERFORM 50000 TIMES
CONTINUE
END-PERFORM.
ACCEPT END-TIME FROM TIME.
DISPLAY "処理終了時刻: " END-TIME.
STOP RUN.