予約語を持たないPL/Iの美学と、文字列検索の「裏側」
メインフレームの現場で長くPL/Iを書いてきた人間にとって、他のモダン言語(JavaやC#など)のソースコードを見たとき、最初に感じる違和感は何なのだろうかと思うことがある。その一つが「予約語(Keyword)」の多さだ。
一般的なプログラミング言語では、`if` や `class`、さらには `index` や `verify` といった頻出の識別子すら予約語として厳格に管理され、変数名として使うことは許されない。しかし、創世記のIBMメインフレームを支えるために設計されたPL/Iの仕様は、ある意味で非常に懐が深い。PL/Iには「文脈依存のキーワード(Contextual Keywords)」という概念が採用されており、厳密な意味での「予約語」が存在しない。
例えば、`INDEX` や `VERIFY` といった組み込み関数名であっても、変数名として宣言して使うことが语法上は可能である。(もちろん、可読性の観点や後続のメンテナンスを考えれば、そんな悪趣味なコーディングをするプログラマは即座にコードレビューで差し戻されるべきだが)。この「文脈によって意味を解釈する」というコンパイラの柔軟な字句解析(Lexical Analysis)の裏側で、PL/Iオプタイザ(Compiler Optimizer)は、私たちが記述した高水準の構文を、S/370アーキテクチャ以降のハードウェア命令へと極限まで効率的に翻訳している。
今回は、基幹システムのバッチ処理や大規模データ移行において、パフォーマンスのボトルネックになりやすい `INDEX` および `VERIFY` 関数の内部実装、そしてCPUサイクルを削り出すための最適化技法について、メインフレームの深層から紐解いていこう。
—
1. 文字列検索の裏で何が起きているのか?(TRT命令とハードウェアの現実)
金融機関の勘定系や、数千万件を誇る大規模な保険契約バッチにおいて、文字列の探索やバリデーションは日常茶飯事だ。ここで私たちが何気なく記述する `INDEX(A, B)` や `VERIFY(A, ’09’X)` は、コンパイラによってどのようなマシン語に変換されているだろうか。
PL/Iオプタイザが最適(`OPTIMIZE(2)` 以上)に設定されている場合、これらの文字列関数は、単なる文字単位のループ(LOOP)や愚直な比較処理には展開されない。IBM汎用機のアーキテクチャにおける真骨頂、すなわち TRT(Translate and Test)命令 や NCMP / CLCL命令 といったファームウェアレベルのハードウェア命令へ直結する。
TRT命令の圧倒的な優位性
`VERIFY` 関数を例に取ろう。指定された文字列の中に、許可された文字セット(例えば数字や特定のアルファベット)以外の文字が含まれていないかを検証する処理は、データ移行時のクリーニングバッチや、外部インターフェース電文のバリデーションで頻出する。
これを愚直に `DO` ループで1文字ずつ比較していくと、パイプラインの乱れや分岐命令(Branch)のオーバーヘッドにより、膨大なCPUサイクル(MIPS)を消費する。しかし、TRT命令を活用した内部ルーチンであれば、256バイトのトランスレート・テーブルを参照しながら、条件に一致(あるいは不一致)する最初のバイトをハードウェアの高速ループで一気に突き止める。
1
DCL TARGET_DATA CHAR(1000) INIT(‘ ‘);
DCL ERROR_POS FIXED BIN(31);
DCL VALID_TBL CHAR(256) INIT(/ 初期化ロジック省略 /);
/ 文字列中に数字以外の不正文字が含まれていないかを検証 /
ERROR_POS = VERIFY(TARGET_DATA, ‘0123456789’);
IF ERROR_POS > 0 THEN
/ エラー処理: 検出位置 ERROR_POS における不整合 /
このコードが発するアセンブラレベルの効率性は、JavaやC#の `String.indexOf` や正規表現エンジンがどれだけJITコンパイルで最適化されても、CICSやバッチの基盤上におけるオーバーヘッドの少なさと予測可能な実行時間の面で、依然としてメインフレームが優位性を保ち続ける理由の一つとなっている。
—
2. パフォーマンスに直結する検索アルゴリズムの罠
しかし、どれほどハードウェアが優秀であっても、プログラマの書き方次第でその恩恵は水の泡となる。特に、マイグレーション時のデータ変換プログラムでよく見られるアンチパターンをいくつか挙げよう。
① 毎回可変長文字列(VARCHAR)の長さを再計算させるコスト
PL/Iの `VARYING` 属性を持つ文字列は非常に便利だが、その実態は「2バイトの長さプレフィックス + 実データ本体」という構造になっている。
ループの中で無造作に `INDEX(VAR_STR, ‘ABC’)` を呼び出すと、コンパイラは安全のために毎回長さの評価や境界チェックを挟む場合がある。大量のレコードを処理するバッチ内では、これが累積して無視できないCPUサイクルの無駄遣いとなる。
② 固定長(CHARACTER)への不必要な暗黙的パディング
比較対象の文字列長が異なる場合、PL/Iの仕様に則ってブランクパディング(空白埋め)が暗黙的に発生する。特に `INDEX` 関数を使用する際、検索キーの末尾に不要なブランクが含まれていると、期待したオフセットを返さないだけでなく、無駄な文字比較が発生する。
—
3. 実務で直面するエッジケースとトラブルシューティング
ここで、現場のテックリードや移行エンジニアが頭を悩ませる「リアルな障害事例」を共有しておこう。
ケースA:パックデシマル(COMP-3)の符号反転バグと文字列検索の落とし穴
外部から連携されてきた電文データの中に、本来数値であるべきゾーン十進(DISPLAY)やパック十進(COMP-3)の領域が混ざっていることがある。これをうっかり `CHARACTER` 型として扱い、`INDEX` や `VERIFY` でパースしようとした際、マイコンのエンディアンや文字コード(EBCDICとASCII)の差異、さらにはパックデシマルの末尾ニブル(符号部: `C`, `D`, `F` など)が文字列検索にヒットして思わぬ誤作動を起こす。
特に、メインフレームからオープン系(Java/DB2)へマイグレーションする際、EBCDIC環境下で正常に動作していた `VERIFY` による数値チェックが、移行後のLinux/Java環境(UTF-8/ASCII)で文字コード体系の変更に伴い、予期せぬアベンドやデータ化けを引き起こすケースが後を絶たない。
ケースB:動的メモリ操作(POINTERとBased変数)との組み合わせにおけるアドレス例外(S0C4)
大規模な可変長レコードを効率的にスキャンするため、あらかじめ取得したストレージ(`GET STORAGE`)に対して `POINTER` を渡し、`BASED` 変数経由で `INDEX` 関数を適用する設計を採用することがある。
1
Dcl 1 WORK_AREA Based(P_WORK),
3 REC_LEN Fixed Bin(15),
3 REC_BODY Char(32760) Varying;
/ ポインタの設定誤りやオフセット計算ミスによりストレージ保護例外が発生する例 /
P_WORK = ADDR(RAW_BUFFER);
/ 検索処理 /
Dcl POS Fixed Bin(31);
Pos = Index(REC_BODY, ‘TARGET’);
ここで `REC_BODY` の長さ(`REC_LEN`)が実際のバッファサイズを超えていたり、ポインタ `P_WORK` が不正なアドレスを指している状態で `INDEX` を実行すると、容赦なく S0C4(SYSTEM ABEND 0C4: Protection Exception) が発生し、バッチジョブ全体が異常終了する。動的メモリ操作を伴う文字列検索では、事前の境界値チェック(Bound Check)を怠ると、一瞬で深夜のオペレータ呼び出しを引き起こすことになる。
—
4. コンパイラオプションによる極限の最適化
PL/Iで文字列処理のパフォーマンスを限界まで引き出すためには、ソースコードの書き方と同じくらい、JCL上のコンパイラオプションの指定が死活問題になる。
実務の現場では、以下のオプションが鉄則として組み込まれているか必ず確認すべきだ。
- `OPTIMIZE(2)` または `OPT(FULL)`: ループの不変式の外出し、レジスタ割り当ての最適化、インライン展開をフルに効かせる。
- `TEST(NOCALL,SYM)` の本番環境からの排除: デバッグ用フックが埋め込まれると、ハードウェア命令のパイプライン効率が落ちるため、本番ロードモジュール生成時には必ず `NOTEST` にする。
- `TRUNC(BIN)` の適切な選択: `FIXED BINARY(31)` の変数が機械語レベルでどのように切り詰められるかを制御する。デフォルトの `TRUNC(STD)` は厳密な桁数チェックを行うためオーバーヘッドになることがあるが、データ整合性が担保されている高速バッチでは `TRUNC(OPT)` を検討する余地がある(ただし移行時は互換性に要注意)。
—
アーキテクトとしての提言:レガシー移行における心構え
JavaやC#へのマイグレーションプロジェクトにおいて、「PL/Iの古い文字列関数を、そのままモダン言語のライブラリに置き換えれば終わり」と考えているなら、それは大きな誤りだ。
IBMメインフレームのPL/IオプタイザとS/370アーキテクチャが生み出す実行速度の裏には、ハードウェアとソフトウェアが一体となった緻密なエコシステムが存在する。`INDEX` や `VERIFY` のような一見シンプルな関数であっても、その背後にあるCPUサイクルの消費量、文字コード変換のコスト、そして動的メモリの挙動を深く理解していなければ、移行後のオープン系基盤で「なぜかバッチ処理時間が倍増した」という致命的なパフォーマンス劣化に直面することになる。
コードの表面的な構文をなぞるのではなく、コンパイラが吐き出す機械語とハードウェアの挙動まで見通す目を持つこと。それこそが、真のレガシー移行スペシリアスト、そしてシステムアーキテクトに求められる要件なのである。
