【実務・中級編】MAXおよびMIN関数の比較命令の連鎖 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは、若手エンジニアの皆さん。日々のメインフレーム保守や、終わりの見えないレガシー移行(COBOLやPL/Iのオープン系マイグレーション)の調査、本当にお疲れ様です。

今日も「なぜこのコードはこんな動きをするんだ?」という不可解な現象に頭を悩ませていることでしょう。IBMメインフレームの心臓部で稼働するPL/I(Programming Language One)は、1960年代に生まれた非常に表現力の高い言語です。しかし、その自由度の高さゆえに、書き手によってパフォーマンスや保守性が大きく変わる「諸刃の剣」でもあります。

今回は、PL/Iのデータ制御と最適化の肝となる「MAXおよびMIN関数の比較命令の連鎖」について、コンパイラの裏側の挙動(アセンブラレベルの視点)を交えながら、実務に直結するノウハウを伝授しましょう。

—

1. 識別子の自由度と「予約語を持たない」PL/Iの罠

まず、今回のテーマに入る前に、PL/Iの根底にある極めて重要な仕様についておさらいしておきます。

COBOLなどの言語では、`MOVE`やん`COMPUTE`といった数十・数百の「予約語(Reserved Words)」が存在し、これらを変数名として使うことはできません。しかし、PL/Iには原則として固定の予約語が存在しません。

例えば、`IF`や`THEN`、さらには今回取り上げる`MAX`や`MIN`といった組み込み関数名(BUILTIN)であっても、コンテキスト(文脈)によって変数名や配列名として定義・使用することが可能です。

1
/ まったく文法エラーにならない例 /
DECLARE MAX FIXED BIN(31) INITIAL(100);
DECLARE MIN FIXED BIN(31) INITIAL(0);

/ ここでは MAX は変数として扱われる /
IF MAX > MIN THEN
PUT SKIP LIST(‘OK’);

「おっ、なんて自由で素晴らしい言語なんだ!」と思いましたか?
大間違いです。 現場のシステムアーキテクトとして言わせてもらえば、この仕様は「保守性という観点において最大の爆弾」になり得ます。前任者がうっかり組み込み関数名と同じ名前の変数を作ってしまったがために、コンパイラがどちらを指しているのか迷走し、意図しない最適化漏れや、最悪の場合はコンパイルエラーではなくサイレントバグ(誤った値での処理続行)を引き起こす温床になるのです。

だからこそ、実務のコーディング標準では「組み込み関数名やキーワードを識別子として流用しない」ことを鉄則にしています。これを踏まえた上で、本題である`MAX`および`MIN`関数の実用的な比較命令の連鎖について見ていきましょう。

—

2. 複数引数を持つ MAX / MIN 関数のコンパイラ最適化

バッチ処理において、複数のレコード項目や配列要素から「最大値」や「最小値」を抽出する処理は日常茶飯事です。

素朴なプログラマは、これを以下のように冗長な `IF` 文の連鎖で書きがちです。

1
/ 悪い例:冗長な条件分岐の連鎖 /
IF A >= B & A >= C & A >= D THEN
MAX_VAL = A;
ELSE IF B >= A & B >= C & B >= D THEN
MAX_VAL = B;
/ 以下延々と続く… /

これをやってしまうと、コンパイラが生成する機械語命令(BC: Branch on Condition、CR: Compare Registerなど)が無駄に増え、CPUのパイプラインハザードを引き起こします。VSAMファイルから数百万件のレコードを読み込んでスキャンする夜間バッチにおいて、この数ラインの非効率が何万回も積み重なると、バッチウィンドウ(処理制限時間)をオーバーする致命傷になります。

組み込み関数(BUILTIN)としての MAX / MIN の活用

PL/Iには、強力な組み込み関数として `MAX` と `MIN` が用意されています。これらは単に2つの引数を取るだけでなく、可変長の引数を受け取ることができます。さらに、コンパイラ(Enterprise PL/Iなど)は、これらの関数に対して非常に高度なレジスタ割り付けとインライン展開の最適化を行います。

内部的には、複数の値を効率的なレジスタ間比較(例:`CR`, `LGR` など、z/Architectureの最新命令)に翻訳し、条件分岐の回数を最小限に抑え込むコードを吐き出してくれるのです。

—

3. 実践:VSAM入出力とONユニットを伴うバッチプログラム例

それでは、実際のメインフレーム開発を想定した完全なソースコードを見てみましょう。
このサンプルでは、VSAM(KSDS)から読み込んだ売上データ群に対し、`MAX` / `MIN` 関数をスマートに適用しつつ、万が一の入出力エラーやデータ異常に対する `ONユニット` による例外制御を組み込んでいます。

1
/================================================================/
/ プログラム名: SMAXTST1 /
/ 概要: VSAMレコードの複数数値フィールドからMAX/MINを効率的に算出 /
/================================================================/
SMAXTST1: PROC OPTIONS(MAIN);

/ 組み込み関数の明示的な宣言(BUILTIN属性) /
DECLARE (MAX, MIN) BUILTIN;

/ VSAMファイル(KSDS)の定義 /
DECLARE SALES_FILE FILE RECORD
INPUT
ENVIRONMENT(BUFSZ(4096));

/ レコード構造体の定義 /
DECLARE 1 SALES_REC,
5 EMP_ID CHAR(5),
5 Q1_SALES FIXED DEC(9,2),
5 Q2_SALES FIXED DEC(9,2),
5 Q3_SALES FIXED DEC(9,2),
5 Q4_SALES FIXED DEC(9,2);

/ ワーク変数の定義 /
DECLARE PEAK_SALES FIXED DEC(9,2);
DECLARE BOTTOM_SALES FIXED DEC(9,2);
DECLARE EOF_FLAG BIT(1) INIT(‘0’B);

/ エラー制御用ONユニット(ファイル終了条件の捕捉) /
ON ENDFILE(SALES_FILE)
BEGIN;
EOF_FLAG = ‘1’B;
END;

/ 重大なデータ例外(ゼロ割や数値変換エラーなど)の捕捉 /
ON ERROR
BEGIN;
PUT SKIP LIST(‘ 予期せぬシステムエラーが発生しました ‘);
/ 必要に応じたダンプ採取や異常終了処理をここに記述 /
STOP;
END;

/ ファイルオープン /
OPEN FILE(SALES_FILE) INPUT;

PUT SKIP LIST(‘— 四半期売上 分析バッチ処理 開始 —‘);

/ メインループ:VSAMからの逐次読み込み /
DO WHILE(^EOF_FLAG);

READ FILE(SALES_FILE) INTO(SALES_REC);

IF EOF_FLAG THEN LEAVE;

/————————————————————/
/ MAX / MIN 関数の連鎖呼び出し /
/ 4つの四半期売上から、最大値と最小値をワンタッチで算出する /
/ コンパイラがレジスタ間比較に最適化するためBC命令が激減する /
/————————————————————/
PEAK_SALES = MAX(Q1_SALES, Q2_SALES, Q3_SALES, Q4_SALES);
BOTTOM_SALES = MIN(Q1_SALES, Q2_SALES, Q3_SALES, Q4_SALES);

/ 結果の出力 /
PUT SKIP EDIT
(‘社員ID: ‘, EMP_ID,
‘ | 最高売上: ‘, PEAK_SALES,
‘ | 最低売上: ‘, BOTTOM_SALES)
(A, A, A, F(10,2), A, F(10,2));

END;

/ ファイルクローズ /
CLOSE FILE(SALES_FILE);

PUT SKIP LIST(‘— 四半期売上 分析バッチ処理 正常終了 —‘);

END SMAXTST1;

—

4. シニアアーキテクトからの実践的なアドバイスとデバッグのコツ

このコード、および `MAX`/`MIN` の連鎖を使用するにあたって、現場で役立つ実践的な知見をいくつか共有しておきます。

1. データ型の混在に注意せよ
`MAX(Q1_SALES, Q2_SALES, …)` の引数に渡すデータ型は、できる限り統一してください。もし `FIXED DEC`(パック十進数)と `FLOAT`(浮動小数点)や `FIXED BIN` が混在していると、コンパイラは暗黙のデータ型変換コード(コンバージョン)を裏で生成します。これがループ内で発生すると、せっかくのレジスタ最適化の恩恵が台無しになります。

2. コンパイルリスト(Listing)のクロック・最適化確認の癖をつけろ
Enterprise PL/Iでコンパイルする際は、必ず `OPT(2)` 以上の最適化オプションを指定し、生成されたリスティングのアセンブラ展開コード(Object Code Listing)を確認してください。冗長な `ICM`(Insert Characters under Mask)や不必要なメモリウムーブが消え、効率的なレジスタ比較命令に置き換わっている様を見るのは、メインフレームエンジニアにとって無上の喜び(変態的ロマン)です。

3. ONユニットとエラーハンドリング
今回のコードのように、ファイル入出力だけでなく算術演算の安全性を担保するために `ON ERROR` や専用の条件名(`ON SIZE` や `ON FIXEDOVERFLOW` など)を適切に配置することは、基幹系バッチの信頼性を保つ上で絶対に省いてはならない定石です。

—

おわりに

PL/Iはその歴史の長さゆえに「古い言語」と揶揄されることもありますが、今日のハードウェア(IBM z16など)のベクトル処理機構や先進的なコンパイラ技術と組み合わされることで、極めて高いパフォーマンスを発揮します。

「予約語を持たない」という自由度の高さに惑わされず、コンパイラの挙動を意識した美しいコードを書くこと。それが、これからのレガシーシステムを支える私たちシニア・ミドル層エンジニアの腕の見せ所です。

今回の解説が、皆さんの日々のバッチ改修やマイグレーション作業の確かな指針となれば幸いです。それでは、次回の現場でお会いしましょう!

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