【実務・中級編】コンパイラオプションによる算術演算の最適化 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近夜間バッチのランタイムが妙に膨らんでいる原因を追ってくれって、運用部から泣きつきがあったんだよ。調べてみたら、やっぱりな……という感じの罠にハマっていた。

メインフレームの現場でPL/Iを書いていると、「動けばいいや」と適当にデータ定義したり、コンパイラの最適化オプション(`OPTIMIZE`)の挙動をナメてかかったりする連中が多い。特に、金融や流通の基幹システムを支える固定小数点数(FIXED BINARY / FIXED DECIMAL)の演算なんて、コンパイラが裏でどんな機械語命令を吐いているかを知らないと、思わぬ性能劣化や、最悪の場合は桁落ち・データ例外を引き起こす。

今日は、コンパイラオプションの`OPTIMIZE`が、固定小数点演算の命令生成、特にパック十進数(DECIMAL)とバイナリ(BINARY)の間で行われる暗黙の変換や、`CVB`(Convert to Binary)、`CVD`(Convert to Decimal)といったハードウェア命令の生成にどう影響を与えるか、現場のリアルな知見を交えて徹底的に解説してやる。しっかりついてきな。

—

1. 現場の現実:なぜ `OPTIMIZE` と固定小数点数がバッチの命取りになるのか

メインフレームのCPU(z/Architecture)は、十進数演算(`PACKED DECIMAL`)をハードウェアで直接バリバリ処理できる化け物のようなアーキテクチャを持っている。VSAMファイルやDB2のテーブルから読み込んだデータは、大抵が`FIXED DECIMAL`(ゾーン十進数やパック十進数)だ。

しかし、PL/Iの内部演算でループカウンタや算術計算を行う際、何も考えずに`FIXED BINARY`(2進数)と混在させると、コンパイラは親切心(あるいは仕様の強制)から、次のようなデータ変換の機械語命令を挟み込む。

  • `CVB` (Convert to Binary): パック十進数(またはフルワード)を2進数レジスタに変換する。
  • `CVD` (Convert to Decimal): 2進数レジスタの値をパック十進数に戻す。

この `CVB` や `CVD` は、何百万回、何千万回と回る巨大バッチのループ内では深刻なボトルネックになる。ここでコンパイラオプションの `OPTIMIZE(2)` や `OPTIMIZE(FULL)` が効いてくると、不要なレジスタ退避や冗長な変換命令がごっそり削ぎ落とされる。だが、逆に `OPTIMIZE(OFF)` や初期値のままで開発環境と本番環境のコンパイル条件がズレていると、テストでは気づかない性能劣化や、思わぬオーバーフローを招くことになるのだ。

—

2. 実践コード:VSAM入出力と最適化を意識した算術演算

百聞は一見にしかずだ。実際の商用バッチプログラムを想定したコードを見てみよう。
VSAM(KSDS)からレコードを読み込み、単価(`FIXED DECIMAL`)と数量(`FIXED BINARY`)の掛け算を行い、売上金額を計算して別のファイルに書き出す処理だ。

1
/——————————————————————– /
/ 顧客別売上計算バッチプログラム /
/ 特徴: VSAMアクセスとFIXED DEC/BIN混在演算、ONユニットによる例外制御 /
/——————————————————————– /
SALES_CALC: PROC OPTIONS(MAIN);

/ — 1. ファイル定義 (VSAM KSDS) — /
DCL IN-FILE FILE RECORD INPUT
ENVIRONMENT(VSAM);
DCL OUT-FILE FILE RECORD OUTPUT
ENVIRONMENT(VSAM);

/ — 2. 構造体定義:レコードレイアウト — /
DCL 1 IN-REC,
5 CUST-ID PIC ‘9(05)’, / 顧客ID (文字) /
5 UNIT-PRICE FIXED DEC(9,2), / 単価 (パック十進) /
5 QUANTITY FIXED BIN(31); / 数量 (2進数フル) /

DCL 1 OUT-REC,
5 CUST-ID PIC ‘9(05)’,
5 TOTAL-AMOUNT FIXED DEC(11,2); / 売上合計 /

/ — 3. 制御変数の定義 — /
DCL EOF-FLAG CHAR(1) INIT(‘OFF’);
DCL WK-CALC-AMT FIXED DEC(13,43); / 内部計算用高精度 /

/ — 4. 割り込み条件(ONユニット)の定義 — /
ON ENDFILE(IN-FILE) EOF-FLAG = ‘ON’;

ON CONVERSION
BEGIN;
DISPLAY(‘【警告】数値変換エラーが発生しました。データをスキップします。’);
/ 実務ではここでエラーログファイルへの出力や異常終了コードのセットを行う /
GOTO SKIP-RECORD;
END;

ON FIXEDOVERFLOW
BEGIN;
DISPLAY(‘【致命的エラー】固定小数点数オーバーフローが発生しました。’);
SIGNAL ERROR;
END;

/ — 5. 処理メインループ — /
OPEN FILE(IN-FILE) INPUT, FILE(OUT-FILE) OUTPUT;

READ FILE(IN-FILE) INTO(IN-REC);

DO WHILE(EOF-FLAG = ‘OFF’);

/ [重要]

  • FIXED DEC と FIXED BIN の演算。
  • OPTIMIZEオプションが有効な場合、コンパイラはこの乗算における
  • 内部的なCVB/CVDの生成を極限までインライン最適化する。

/
WK-CALC-AMT = UNIT-PRICE QUANTITY;

/ 丸め処理を適用して出力用構造体に代入 /
TOTAL-AMOUNT = ROUND(WK-CALC-AMT, 2);

/ 結果をアウトプットファイルへ書き出し /
WRITE FILE(OUT-FILE) FROM(OUT-REC);

SKIP-RECORD:
READ FILE(IN-FILE) INTO(IN-REC);
END;

CLOSE FILE(IN-FILE), FILE(OUT-FILE);

DISPLAY(‘正常終了:売上計算バッチが完了しました。’);
RETURN;

END SALES_CALC;

—

3. コードの解説とコンパイラ最適化(OPTIMIZE)の深掘り

上記のコード、特に計算ロジックの部分をアーキテクトの視点で解説しておこう。

データの型違いによる隠れたコスト

コード内で `UNIT-PRICE` は `FIXED DEC(9,2)`、`QUANTITY` は `FIXED BIN(31)` として定義されている。
型が異なるオペランド同士で算術演算を行う場合、PL/Iコンパイラはどちらかの型へ寄せるためのコードを生成する。
ここで `OPTIMIZE(FULL)` などの高位最適化が指定されていると、コンパイラはループ内で何度も行われる型変換をループ外にホイスティング(外出し)したり、汎用レジスタと浮動小数点/十進演算器のパイプラインを効率化する機械語シーケンスを組み立ててくれる。

逆に `OPTIMIZE(OFF)` でコンパイルすると、くだらないレジスタのロード・ストア(`ST` と `L`)、そして無駄な `CVD`/`CVB` が毎回のループで実行され、CPUサイクルの無駄遣いになる。基幹システムの夜間バッチが何時間も余計にかかる原因の多くは、こうした「最適化無効」の状態で巨大なVSAMファイルを回していることにあるんだ。

ONユニットによる例外制御の重要性

実務において、古いデータや外部から流入した不正なデータによって `CONVERSION` エラーや `FIXEDOVERFLOW` が起きるのは日常茶飯事だ。
上記のコードでは、`ON CONVERSION` を使ってエラー行を安全にスキップする設計にしている。
ただし、過剰なONユニットの記述は最適化の足を引っ張ることがある。コンパイラは例外が発生した際のレジスタの整合性(プレシジョンやステータス)を保たなければならないため、最適化の余地が狭まるのだ。パフォーマンスがシビアなセクションでは、データバリデーションを事前に行い、例外ハンドラに頼らない構造にすることもプログラマの腕の見せ所だ。

—

4. ベテランからの実践アドバイス:マイグレーション・保守時の注意点

これから既存のPL/Iプログラムを新しいコンパイラ(IBM Enterprise PL/Iなど)に移行したり、マイグレーションプロジェクトに参入したりするなら、次の3点を必ず頭に叩き込んでおいてほしい。

1. JCLのコンパイルパラメータを確認しろ
コンパイルJCLのPARMに `OPT(2)` や `OPT(FULL)` が指定されているか確認せよ。テスト環境で `OPT(0)`(最適化なし)でビルドしたバイナリをそのまま本番に乗せると、性能要件をクリアできなくて青ざめることになる。
2. プレシジョン(精度)のルールを曖昧にするな
`FIXED DEC` や `FIXED BIN` の演算結果の精度(プレシジョン)は、PL/Iの言語仕様で厳密に決まっている。中途半端な長さを指定すると、コンパイラが自動的に最大長(15桁や31桁)までの拡張コードを生成し、これもまたパフォーマンス悪化の原因になる。
3. ビルトイン関数(`BUILTIN`)を味方につけろ
`ROUND` や `ABS` などのビルトイン関数は、コンパイラによってハードウェア命令(あるいは極めて効率的なインラインコード)に展開される。自前で変な端数処理ルーチンを自作するより、コンパイラの最適化エンジンを信用して標準関数を使う方が圧倒的に安全で速い。

メインフレームのレジスタやアセンブラレベルの動きを少し想像するだけで、書くべきPL/Iのコードは劇的に変わる。
「動くコード」から「洗練された、CPUに愛される高効率コード」へ。今日の話を、次のバッチ改修の現場でぜひ活かしてくれ。

タイトルとURLをコピーしました