【実務・中級編】算術演算における型プロモーション(データ変換)の優先順位 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近夜間バッチのランタイムが妙に膨らんで、「なんだこのCPU時間の消費量は……」と頭を抱えていないか?

メインフレームの現場で長く生きていると、新人が良かれと思って書いた、あるいは世代交代で入ってきた移行ツールが吐き出した「一見何気ない計算式」が、実はコンパイラの裏でとんでもないデータ変換の嵐を引き起こしている現場に何度も遭遇する。

特にPL/Iという言語は、C言語やJavaのような「きつい型制約」のノリで触ると、その懐の深さ(=何でも勝手にやってくれる優しさ)裏切られて痛い目を見る。今回は、PL/Iの算術演算における型プロモーション(型昇格)の優先順位と、それが引き起こすパフォーマンスの罠、そして実務で身を立てるエンジニアなら絶対に押さえておきたい防衛策について、徹底的に叩き込んでやろう。

—

1. PL/Iの「予約語を持たない」自由さと、その代償

まず大前提として、PL/Iの最大の特徴であり、同時にパーサーを泣かせ、コンパイラの最適化を複雑にする要因が「PL/Iには真の予約語(Reserved Words)が存在しない」という仕様だ。

`IF` や `THEN` さえもコンテキストによっては変数名として定義できてしまう(もちろんそんな狂った真似をする奴はいないが)。この柔軟な構文規則のせいで、コンパイラは変数のデータ型や属性をコンパイル時に厳密に評価し、異なる型が混在する算術式に出会ったとき、独自の「型プロモーションの優先順位」に従って暗黙の型変換(Implicit Conversion)を裏で実行する。

これがバッチ処理のループ内で何百万回も発生したとき、何が起きるか。
CPUは算術演算そのものよりも、10進数(PACKED DECIMAL / ZONED DECIMAL)と固定小数点二進数(FIXED BINARY)、あるいは浮動小数点数(FLOAT)の間を行き来するデータ形式の変換(コンバージョン・ルーチンの呼び出し)に多大なサイクルを奪われることになる。

—

2. 算術演算における型プロモーションの優先順位

PL/Iが演算を行う際、異なるデータ型が混在していると、より「上位」のデータ型へ自動的に昇格(プロモーション)される。この優先順位を頭に叩き込んでおけ。

一般的に、PL/Iの算術型は以下の順序でランク付けされている(下に行くほど強い)。

1. FIXED DECIMAL(パック十進数 / ゾーン十進数)
2. FIXED BINARY(固定小数点二進数 / フルワード・ハーフワード)
3. FLOAT(浮動小数点数)

さらに、同じ基数(10進か2進か)の中でも、精度(プレシジョン)やスケール(小数点位置)の違いによって、コンパイラは中間ワークエリアのサイズを動的、あるいは静的に決定する。

ここで恐ろしいのは、VSAMファイル(KSDS)のレコードレイアウト定義(01レベルに相当する構造体)から直接読み込んだ項目だ。大抵のCOBOLからの移行資産や古いPL/I定義では、金額や数量項目に `FIXED DEC(11,2)` などのパック十進数が使われている。これを、プログラム内のループカウンタやインデックスである `FIXED BIN(31)` と直接演算させると、コンパイラはせっせと10進数と2進数の相互変換コードを生成しやがる。

—

3. 実践コード:暗黙的変換の罠とBUILTIN関数による最適化

百聞は一見にしかずだ。実際のメインフレームのバッチ処理を想定した、VSAM(KSDS)のレコードを読み込み、単価と数量を掛け合わせて総額を計算するプログラムの断片を見てみよう。

1
/ /
/ プログラム名: CALC01SP /
/ 概要: VSAM入力データの算術演算における型プロモーションの検証 /
/ /
CALC01SP: PROC OPTIONS(MAIN);

DCL VSAM-IN-FILE FILE RECORD INPUT
ENVIRONMENT(BUFSZ(4096));

DCL 1 VSAM-RECORD,
5 REC-KEY CHAR(6),
5 REC-QTY FIXED DEC(7,0), / 数量: パック10進数 /
5 REC-PRICE FIXED DEC(9,2); / 単価: パック10進数 /

DCL W-TOTAL-AMT FIXED DEC(13,2) INIT(0); / 累計総額 /

/ 内部計算用の最適化変数(FIXED BINARYで定義) /
DCL W-IDX FIXED BIN(31) INIT(0);
DCL W-CALC-WORK FLOAT BIN(53); / 浮動小数点ワーク /

DCL EOF-FLAG CHAR(1) INIT(‘N’);

/ ファイルオープン /
OPEN FILE(VSAM-IN-FILE);

ON ENDFILE(VSAM-IN-FILE) EOF-FLAG = ‘Y’;

READ FILE(VSAM-IN-FILE) INTO(VSAM-RECORD);

DO WHILE (EOF-FLAG = ‘N’);
W-IDX = W-IDX + 1;

/ 【アンチパターン】 /
/ FIXED DEC と 異なる属性の混在による暗黙的変換の発生 /
/ W-TOTAL-AMT = W-TOTAL-AMT + (REC-QTY REC-PRICE); /

/ 【推奨アプローチ】 /
/ 明示的な型変換(BUILTIN関数)によるコンパイラ制御 /
/ BINARY関数やFLOAT関数を使い、演算の土俵を統一する /

W-CALC-WORK = FLOAT(REC-QTY, 53) FLOAT(REC-PRICE, 53);

/ 最終的な集計はFIXED DECに戻して精度を保証する /
W-TOTAL-AMT = W-TOTAL-AMT + FIXED(W-CALC-WORK, 13, 2);

READ FILE(VSAM-IN-FILE) INTO(VSAM-RECORD);
END;

CLOSE FILE(VSAM-IN-FILE);

PUT SKIP LIST(‘TOTAL PROCESSED: ‘, W-IDX);
PUT SKIP LIST(‘TOTAL AMOUNT : ‘, W-TOTAL-AMT);

RETURN;
END CALC01SP;

コードの解説と現場の勘所

1. `FIXED DEC` 同士の掛け算の重み:
`REC-QTY`(`FIXED DEC(7,0)`)と `REC-PRICE`(`FIXED DEC(9,2)`)の乗算結果は、PLの言語仕様上、精度が自動拡大される。このときコンパイラは、オーバーフローを防ぐために十分な桁数を持つ中間レジスタを割り当てるが、基数が異なる変数や複雑な混在式になると、コード生成の効率が悪化する。
2. `FLOAT` や `BINARY` への明示的キャスト(BUILTIN関数の活用):
あらかじめ `FLOAT( , 53)` などのビルトイン関数を使って演算の土俵を浮動小数点(または `FIXED BIN`)に統一してやれば、コンパイラは迷いなく高速なハードウェア命令(FPU命令など)を選択できる。
3. ONユニットと例外処理の併用:
もし万が一、VSAMデータが化けていてデータ例外(DATA EXCEPTION / S0C7類似のPL/I側コンディション `CONVERSION` や `FIXEDOVERFLOW`)が発生した場合、無暗な型変換を入れているとデバッグ時にどの変換でスピンしたのか分からなくなる。算術演算の周辺には必ず `ON CONVERSION` などのONユニットを適切に配置し、異常終了時のトレーサビリティを確保しておくのがプロの仕事だ。

—

4. パフォーマンス低下を回避するための実践チェックリスト

大規模バッチのチューニングや、オープン系・他機種からのマイグレーション案件で絶対に守るべき鉄則をまとめておく。

  • ループ内の暗黙的変換を排除せよ

`DO WHILE` や `DO I = 1 TO 1000000` のような高頻度で実行されるループ内部で、`FIXED DEC` と `FIXED BIN` を直接演算させるな。インデックス変数は必ず `FIXED BIN(31)`(またはアーキテクチャに合わせて `FIXED BIN(63)`)で統一しろ。

  • プレシジョン(精度)の無駄な拡大を防げ

PL/Iは演算結果の精度を自動的に最大化しようとする性質がある。必要以上に大きな桁数(例えば `DEC(31,15)` など)を指定すると、ハードウェアのネイティブなレジスタサイズを超えてしまい、ソフトウェア・エミュレーションによるサブルーチン呼び出しが発生し、パフォーマンスが急降下する。

  • コンパイラオプション(OPTIMIZE)との関係を理解せよ

IBM Enterprise PL/Iコンパイラを使用する場合、`OPTIMIZE(FULL)` や `ARCH(xxxx)`(アーキテクチャの指定。Z14やZ16などの最新プロセッサー命令の活用)を有効にしていれば、ある程度の暗黙的変換は最適化される。しかし、言語仕様レベルで型が一致しているコードに勝る最適化はない。コンパイラに無駄な気を使わせるな。

—

シニアからのメッセージ

メインフレームの寿命は長い。そして、そこで動き続けるPL/Iのコードもまた、何十年もの歴史を背負っている。
「動いているから触らない」ではなく、「なぜこの型定義なのか」「この計算式でコンパイラは何をしているのか」をコードの向こう側まで透視できるようになって初めて一人前のメインフレーム・アーキテクトだ。

今回の型プロモーションのルールとBUILTIN関数の活用法をマスターすれば、君が手掛けたバッチ処理のCPU時間を劇的に削り、運用部門や上司からの評価をゴッソリ勝ち取ることができるはずだ。次の改修では、ぜひコードの「型」にこだわり抜いてみてくれ。期待しているぞ。

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