こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネス寄りの言語をバリバリ書いてきた方にとって、IBMメインフレームの「PL/I(ピーエルワン)」という名前を聞くだけで、なんだか古めかしい黒い画面と難解なエラーメッセージが脳裏に浮かび、身構えてしまうかもしれませんよね。
「なんだか変な書き方をするらしい」「予約語がないってどういうこと?」——そんな不安を抱えているあなたへ。大丈夫です、怖くないですよ。一つずつ紐解いていけば、PL/Iがいかに合理的で、時に驚くほどエレガントな言語であるかが分かっていきます。
今回は、PL/Iのちょっとユニークな「識別子のルール」に軽く触れつつ、実務のバッチ処理やマイグレーション調査で思わず唸る「MAXおよびMIN関数の比較命令の連鎖」と、その裏側にあるコンパイラの優しさ(最適化)について、たっぷりとお話ししていきますね。
—
1. そもそもPL/Iには「予約語」がないって本当?
JavaやCOBOLを触ったことがある方なら、「IF」「THEN」「DATA」といった言葉がシステムにあらかじめ予約されていて、変数名に使えない(コンパイルエラーになる)という経験があるはずです。COBOLなんて、予約語の多さにげんなりしますよね。
ところが、PL/Iには文脈依存の原則(Contextual Keywords)という強力な仕組みがあり、厳密な意味での「予約語」が存在しません。
どういうことかと言うと、例えば `IF` という単語ですら、コンパイラは前後の文脈を見て「あ、ここで出てきた `IF` は条件分岐の命令だな」「おや、こっちの `IF` は変数の名前だな」と勝手に判断してくれるのです。
1
/ ぶっ飛んだ変数宣言の例ですが、PL/Iなら怒られません /
DECLARE IF FIXED BIN(31);
DECLARE THEN FIXED BIN(31);
IF = 10;
THEN = 20;
IF IF > 5 THEN
THEN = IF 2;
……さすがにこんな不気味な変数名は実務で書いちゃダメですよ!(レビューで大目玉を食らいます)。でも、「言語仕様としてそれだけ柔軟なんだな」と雰囲気が掴めればバッチリです。変数名(識別子)はアルファベットで始め、文字や数字、アンダースコアを組み合わせて自由に作れます。Javaの命名規則と大差ないので、そこは安心して安心してくださいね。
—
2. 今日のメインテーマ:MAX・MIN関数の比較命令の連鎖と最適化
さて、ここからが本題です。
基幹システムのデータ処理をやっていると、「複数の数値の中から、一番大きいもの(あるいは一番小さいもの)を取り出したい」というシチュエーションに幾度となく直面します。
例えば、売上データやセンサーの測定値など、5つの変数(`A`, `B`, `C`, `D`, `E`)を比較して最大値を求めたいとしましょう。
JavaやC言語の感覚だと、泥臭くこう書きたくなりますよね。
// Javaのイメージ:泥臭い比較の連鎖
int maxVal = A;
if (B > maxVal) maxVal = B;
if (C > maxVal) maxVal = C;
if (D > maxVal) maxVal = D;
if (E > maxVal) maxVal = E;
これをそのままCOBOLや古いアセンブラ風に置き換えると、条件分岐(BC: Branch on Condition)が何度も発生し、CPUのパイプラインが乱れまくる原因になります。メインフレームの限られたCPU資源をいかに効率よく使うか、レガシーエンジニアの腕の見せ所です。
ここでPL/Iの登場です。PL/Iには、最初から組み込み関数として `MAX` や `MIN` が用意されています。しかも、この関数は引数を2つだけでなく、いくつでも(可変長で)受け取れるという太っ腹な仕様になっています。
実務で使えるPL/Iコード例
実際のバッチプログラム(PL/I)で、複数変数の最大値をスマートに求めるコードを見てみましょう。
1
/ ————————————————– /
/ 複数項目の最大値(MAX)比較の連鎖サンプル /
/ ————————————————– /
TEST_MAX_CONUM: PROC OPTIONS(MAIN);
/ データの宣言:固定小数点数(31ビット) /
DCL (VAL_A, VAL_B, VAL_C, VAL_D, VAL_E) FIXED DEC(9) INIT(0);
DCL MAX_RESULT FIXED DEC(9) INIT(0);
/ サンプル用に適当な値をセット /
VAL_A = 150;
VAL_B = 1020;
VAL_C = 430;
VAL_D = 9999;
VAL_E = 3300;
/ — ここに注目! MAX関数へ複数の引数を一網打尽で渡す — /
MAX_RESULT = MAX(VAL_A, VAL_B, VAL_C, VAL_D, VAL_E);
/ 結果の出力(メインフレームではSYSPRINTへ出力が定番) /
PUT SKIP EDIT (‘最大の売上データは: ‘, MAX_RESULT) (A, F(10));
END TEST_MAX_CONUM;
—
3. コンパイラは何をしているのか?(アーキテクチャの裏側)
「関数に引数をたくさん並べただけでしょう? 中身で結局ループを回したり、比較命令の連鎖(BC命令の嵐)を生成しているんじゃないの?」
鋭い方はそう思われるかもしれません。しかし、IBMメインフレームのIBM Enterprise PL/Iコンパイラ(およびその前身の最適化コンパイラ)は、そんな単純なコードは吐き出しません。
複数の引数を持つ `MAX(A, B, C, D, E)` をコンパイルするとき、オプティマイザ(最適化機能)は以下のような驚きの裏技を使います。
1. レジスタ間演算の最大活用:
メインフレーム(z/Architecture)の汎用レジスタや、浮動小数点レジスタ、あるいはベクトル命令(VX: Vector Facility)を巧みに使って、メモリへのアクセス回数を極限まで減らします。
2. インライン展開と効率的な比較ツリー:
愚直な「AとBを比較、大き方を保持してCと比較…」という直列処理ではなく、ハードウェアの比較・選択命令(例えば `CR` 命令や、条件付きムーブ命令である `LOCR` やベクトル比較命令など)を駆使し、分岐ペナルティ(Branch Penalty)が最小になるような機械語コードを自動生成してくれます。
つまり、私たちがソースコード上で `MAX(A, B, C, D, E)` とスッキリ書くだけで、コンパイラが裏でIBMメインフレームのハードウェア特性を極限まで引き出した超高速なレジスタ比較コードに仕立て上げてくれるのです。
マイグレーションの現場などで、「昔の人が書いた複雑なIF文の山」をこのPL/Iの組み込み関数に書き換えるだけで、ソースコードが劇的に読みやすくなるだけでなく、オブジェクトのサイズが縮小し、実行速度が向上するケースすらあります。これぞ、レガシー移行の醍醐味ですよね。
—
まとめ
今回は、PL/Iのちょっとユニークな「予約語がない世界」と、複数引数を持つ `MAX`/`MIN` 関数を通じた比較命令の最適化についてご紹介しました。
- 怖がらなくて大丈夫: 識別子や変数のルールは他言語と似ているし、PL/Iは文脈を読んでくれる優しい言語です。
- 泥臭いIF文の連鎖は卒業: 複数の比較には `MAX` や `MIN` の多引数呼び出しを積極的に使おう。
- コンパイラを信じる: 私たちがシンプルに書けば、IBMの優秀なコンパイラがメインフレームのハードウェアをフルに使って最速のコードを吐き出してくれる。
レガシーシステムの世界は、一見すると難解で近寄りがたく見えますが、その下敷きにある思想は非常に合理的で美しいものです。「一つずつ紐解けば簡単」、その気持ちでぜひ日々の開発や調査を楽しんでみてくださいね。
それでは、次回のメインフレーム談義でお会いしましょう!
