こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいは従来型のビジネス言語をバリバリ書いてこられた方にとって、IBMメインフレームの「PL/I(ピーエルアイ)」という言語は、最初にソースコードを見たとき、ちょっとした「異世界感」があるかもしれません。
特に、PL/IにはJavaのような厳密な「予約語(キーワード)」の概念が薄く、「えっ、変数名にそんな名前つけちゃっていいの!?」と驚くような自由度を持っています。この柔軟性の高さがPL/Iの大きな魅力なのですが……、ひとたびコンパイラの「最適化」が絡んくると、現場のエンジニアを本気で悩ませるトラップに変貌することがあります。
今回は、基幹システムの現場でバッチ改修や夜間バッチのダンプ解析に挑むあなたへ向けて、コンパイラオプション `OPTIMIZE(2|3)` が引き起こす「レジスタ割り当ての魔法」と、それに伴うデバッグの裏側を、優しく紐解いていきましょう。怖くないですよ、一つずつ見ていけば必ず理解できますからね!
—
1. そもそもPL/Iの識別子ってどうなっているの?
JavaやCOBOLでは、`if` や `return`、`MOVE` などのキーワードは変数名として使えない「予約語」になっていますよね。しかし、PL/Iの設計思想はこうです。
> 「文脈から判断するから、名前なんて君の自由につけなさい」
例えば、PL/Iでは `IF` という名前の変数を作ることすら可能です(もちろん、そんな読みづらいコードは誰も書きませんが!)。コンパイラは、それが「制御文の `IF`」なのか「変数 `IF`」なのかを、前後の文脈(コンテキスト)から完璧に読み取ります。
この懐の深さは素晴らしいのですが、コンパイラ側からすると「この変数はどこで使われていて、どこに格納すべきか」を解析するのに、なかなかの頭脳労働を要求されます。そこで登場するのが、コードを極限まで効率化する最適化オプションです。
—
2. コンパイラオプション `OPTIMIZE(2|3)` の正体
私たちが日々書くPL/Iのソースコードは、コンパイラによって機械語(マシン語)に翻訳されます。この時、コンパイラに「賢くしてね」と指示するのが `OPTIMIZE`(省略形 `OPT`)オプションです。
- `OPTIMIZE(0)` または `NOOPTIMIZE`:
素直な翻訳。書いた通りの順番でメモリにアクセスし、機械語にします。デバッグは非常に楽ですが、実行速度は遅くなります。
- `OPTIMIZE(1)`:
基本的な最適化。少しだけコードを整理します。
- `OPTIMIZE(2)` / `OPTIMIZE(3)`:
ここからが本番です! コンパイラがコードを徹底的に分析し、CPUの「レジスタ(超高速な記憶場所)」を変数の置き場としてフル活用します。
レジスタ割り当てってなに?(例え話)
イメージしてみてください。
あなたは大きな倉庫(メインフレームの主記憶=メモリ)で作業をしています。倉庫の棚からちまちま部品を取ってくるのは時間がかかりますよね。
そこで、よく使うドライバーやネジを、自分の「作業ズボンのポケット(CPUレジスタ)」に常駐させることにしました。これなら手を伸ばすだけで一瞬で作業ができます。これが「レジスタ割り当て」です。
`OPTIMIZE(2)` や `OPTIMIZE(3)` を指定すると、コンパイラは「この変数はこのループ内で何度も使われるから、ずっとレジスタに入れておこう!」と判断し、メモリ上の値を更新するのをサボって、ずっとポケットの中の値だけで計算を進めてしまうのです。
—
3. 実務で遭遇する「消えた変数」の恐怖(ダンプ解析の罠)
夜間バッチが異常終了(ABEND)し、システムから「スナップダンプ(SYMDUMP等)」が出力されました。
「よし、原因を特定するために変数の値を確認しよう!」とダンプリストを開いたあなたを、こんな絶望が襲います。
> 「あれ……? 変数 `W-COUNT` の値が、メモリ上に見当たらない(あるいは変なゴミが入っている)!?」
これが、`OPTIMIZE(2|3)` によって引き起こされるレジスタ最適化の罠です。
コンパイラは「どうせこの瞬間、メモリ上の `W-COUNT` は誰も見ていないから、値はCPUのレジスタの中にしまっておけばいいや」と判断しています。しかし、異常終了した瞬間のスナップダンプが記録するのは、基本的に「メモリ(ストレージ)上の値」です。
結果として、レジスタの中に隠されたままの最新値はダンプに反映されず、デバッガで変数を覗いても「OPTIMIZED OUT(最適化により除外されました)」なんて冷たいメッセージが表示されることになります。Javaのデバッグに慣れている方だと、「えっ、コードには確実に存在している変数なのに、なぜ見えないの!?」とパニックになりがちです。
—
4. 現場で役立つ!PL/Iコードと最適化への対策
実際のバッチプログラムで、どのようにデータが宣言され、最適化の影響を受けるのかを見てみましょう。以下は、日計集計を行う簡単なPL/Iのサンプルコードです。
1
——————————————————————
- 集計バッチプログラムのサンプル
——————————————————————
PAYROLL_CALC: PROC OPTIONS(MAIN);
/ データ定義:固定小数点数と作業用変数 /
DCL W-TOTAL-AMT PIC ‘9(9)V99’ DECIMAL(11,2) INIT(0);
DCL W-COUNT FIXED BIN(31) INIT(0);
DCL EOF_FLG CHAR(1) INIT(‘N’);
/ ループ処理:OPTIMIZE(2)以上では W-COUNT がレジスタに常駐しやすい /
DO WHILE (EOF_FLG = ‘N’);
CALL READ_TRANSACTION; / トランザクション読み込み処理(仮) /
IF TRANSACTION_DATA.STATUS = ’99’ THEN
EOF_FLG = ‘Y’;
ELSE
W-COUNT = W-COUNT + 1; / ここでレジスタ上の値がインクリメントされる /
END;
PUT SKIP LIST (‘TOTAL COUNT = ‘, W-COUNT);
RETURN;
END PAYROLL_CALC;
このコードを `OPTIMIZE(2)` でコンパイルした場合、`W-COUNT` の値はループが回っている間、ずっとCPUのレジスタ内で処理され、メモリ上の `W-COUNT` が書き戻されるのはループを抜けた瞬間だけ、あるいは条件が揃った時だけになることがあります。
💡 トラブルシューティングの知恵袋
もしあなたが今、本番障害の調査や、複雑なレガシーバッチの改修で「変数の値が追えない!」と困っているなら、以下の手法を試してみてください。
1. 調査用のコンパイルは `OPTIMIZE(0)`(または `NOOPT`)を使う
バッチの不具合調査のために一時的にコンパイルオプションを `OPTIMIZE(0)` に落としてビルドし直します。これで変数が確実にメモリ上に展開されるため、ダンプやトレーサビリティが劇的に向上します(※本番稼働時はパフォーマンスに影響するため、必ず元の最適化レベルに戻してくださいね)。
2. どうしても最適化をかけたままで追いたい変数は `STATIC` や `volatile` 的な挙動を意識する
PL/IにはC言語の `volatile` のような厳密な修飾子はありませんが、外部から参照される変数や、ポインタ経由で操作される変数は、コンパイラが勝手にレジスタへ隠蔽しにくくなります。
—
まとめ
PL/Iの柔軟な構文規則と、強力なコンパイラ最適化 `OPTIMIZE(2|3)` は、メインフレームの限られたリソースで凄まじい処理速度を叩き出すための「先人たちの知恵と工夫」の結晶です。
最初は「変数が見えない!」「ダンプが読めない!」と戸惑うこともあるかもしれませんが、「あ、今この変数はCPUのポケット(レジスタ)の中で元気に走り回っているんだな」というイメージを持てるようになると、レガシーシステムの挙動が手に取るように分かるようになります。
基幹システムのモダナイゼーションや保守において、この「ハードウェアとコンパイラの動きを読む力」は、あなたを強力に支える武器になります。
焦らず、一つずつ、レガシーの奥深い世界を楽しんでいきましょう!
