【テクニカル・上級編】SEARCH関数のINDEX関数との性能比較 – PL/Iの基本構文とデータ制御実践ガイド

伝説の構文仕様「予約語なし」の世界と、現代に遺された最適化の罠

メインフレームの現場で長年PL/Iコードを触ってきた人間なら一度は直面する、あの独特の空気感がある。JavaやC#、あるいは現代の言語に慣れ親しんだ若手エンジニアが、PL/Iのソースコードを見て最初に驚くのは「予約語(Reserved Words)がほぼ存在しない」という事実だ。

1
/ PL/Iにおける衝撃的な代入文の例 /
IF IF = THEN THEN THEN = ELSE;
ELSE ELSE = THEN;

冗談のようだが、これはコンパイルエラーにならない。PL/Iの文法において、`IF`や`THEN`、`ELSE`といったキーワードは「文脈依存のキーワード(Contextual Keywords)」に過ぎない。コンパイラは前後の文脈やピリオド、セミコロンの配置から「あ、これは変数名だな」「ここは制御文だな」と文脈判断している。

この圧倒的な柔軟性と自由度は、初期のメインフレーム設計思想の名残りであり、同時に複雑なマクロ展開や動的メモリ操作(POINTER、BASED変数)と組み合わさった瞬間、コンパイラ泣かせの高度な最適化対象へと変貌する。

今回は、この自由度の高いPL/Iにおける文字検索の二大巨頭、`INDEX`関数と`SEARCH`関数を取り上げる。単なる「使い方の違い」ではない。アセンブラレベルの命令セット選択、そして現代のオープン系移行(Java/C#)におけるアーキテクチャ設計まで、基幹システムの屋台骨を支える知見を極限まで掘り下げていこう。

—

1. INDEX関数とSEARCH関数の本質的違い:アセンブラレベルの挙動

基幹システムのバッチ処理で、膨大な固定長レコード(例えば、全銀協フォーマットの電文や、数万バイトに及ぶトランザクションログ)から特定コードをスキャンする処理は日常茶飯事だ。ここで「特定の1文字、あるいは部分文字列の位置を探す」ために、どちらの関数を選択するかでCPU使用率(SCT)が跳ね上がることがある。

INDEX関数の正体:ハードウェア支援の高速スキャン

`INDEX(string, substring)` は、指定された文字列の中で部分文字列が最初に現れる位置を返す。
IBM Enterprise PL/Iコンパイラは、この関数に遭遇すると、ターゲットアーキテクチャ(z/Architecture)の強力な文字列操作命令、例えば `CLST` (Compare Logical String) や `SRST` (Search String) といったマシーンインストラクションに直結するコードを生成する。

`SRST`命令は、指定されたレジスタに開始アドレスと終了アドレス(または終了コード)をセットし、ハードウェアレベルの高速ループで一致するバイトを捉える。文字通り、メインフレームのハードウェアの恩恵を最大限に受けるため、単純な部分文字列検索において`INDEX`の右に出るものはいない。

SEARCH関数の正体:動的文字集合の探索

一方、`SEARCH(string, char-set)` は何をするものか。
これは、「`char-set`(検索対象の文字群)に含まれるいずれかの文字が、`string`のどこにあるか」を探す関数である。文字の部分一致ではなく、「複数の候補文字のいずれか(OR条件)」をスキャンするためのものだ。

この挙動の違いを甘く見ていると、以下のようなアンチパターンを生む。

1
DCL TARGET_STR CHAR(100) BASED(P_STR);
DCL POS FIXED BIN(31);

/ 【アンチパターン】単一文字の検索に SEARCH を使っているケース /
/ 本当は INDEX(‘ABCDEF’, ‘X’) で十分なのに、不要な文字集合を作っている /
POS = SEARCH(TARGET_STR, ‘X’);

`SEARCH`関数は、内部的に「指定された文字集合のテーブル(またはビットマップ等)」を構築するか、あるいは複数文字を逐次判定するためのインラインループを展開する。そのため、単一文字(または完全な部分文字列)の検索であっても、`INDEX`命令ではなく、より汎用的でオーバーヘッドの大きい処理ルーチンをアセンブラレベルで選択してしまうのだ。

—

2. ベース変数とポインタによる動的メモリ操作と検索の罠

基幹システムのオンライン(CICS)や大規模バッチでは、ストレージの節約と高速化のために、`BASED`変数とポインタを使ったストレージの直接マッピングが多用される。

1
DCL 1 CICS_COMM_AREA BASED(P_COM),
3 処理区分 CHAR(1),
3 可変長データ CHAR(32767);

DCL P_COM POINTER;
DCL SEARCH_VAL CHAR(1) VALUE(‘X’);
DCL FOUND_POS FIXED BIN(31);

ここで、CICSの通信領域(COMMAREA)やDB2からのカーソルフェッチで得た巨大なポインタ領域に対して検索を行う際、データ長(LENGTH)の指定を誤ると、伝説の S0C4アベンド(Protection Exception) が発生する。

ダンプ解析の現場から:S0C4アベンドの真相

`INDEX`や`SEARCH`の引数に渡す文字列の長さに、動的計算のミスで実際のストレージ長を超える値を指定した場合、コンパイラが生成したスキャン命令(`SRST`等)は、容赦なく割り当てられていないメモリ領域の向こう側へと突き進む。
結果として、アクセス権限のないページに触れ、システムは容赦なくABEND (System Code: 0C4) を吐いて異常終了する。

レガシー移行のプロジェクトにおいて、JavaやC#へこのロジックを移植する際、最もバグの温床になるのがここだ。
Javaの `String.indexOf()` や `Matcher` は境界チェック(Bounds Checking)が厳密に行われるため、元々のPL/Iプログラムが持っていた「領域外すれすれのポインタ操作」が、モダン言語では例外(`IndexOutOfBoundsException`)として即座に検知されるか、あるいは安全な範囲で切り捨てられる。移行時には、PL/I側でどのようなデータ長(LENGTH)が暗黙的に保証されていたかを徹底的に洗い出す必要がある。

—

3. コンパイラオプションによる最適化とパックデシマルの影

IBM Enterprise PL/Iコンパイラの最適化レベル(`OPTIMIZE(2)` や `OPTIMIZE(3)`)は、文字検索関数のアセンブラコード生成に劇的な影響を与える。

高レベルの最適化が有効な場合、コンパイラはループアンロール(Loop Unrolling)を行い、定数長の文字列検索において分岐命令を極限まで削減する。しかし、ここで注意しなければならないのが、データベース(DB2)との連携や、パックデシマル(`FIXED DECIMAL`)を絡めたデータ変換時のエッジケースだ。

1
DCL 1 DB2_RECORD,
3 従業員ID CHAR(5),
3 給与パック FIXED DEC(9,2); / 内部表現はコンパイルオプションに依存 /

もし、`CHAR`型以外のデータ型(例えば、コンパイルオプションで符号の扱いが異なるパックデシマルやゾーンデシマル)が混在する領域を、安易に`SEARCH`関数等のスキャン対象としてキャストした場合、パディング文字(空白やヌル文字、あるいはゾーンの`X’F0’`など)との比較で予期せぬ不一致が発生する。

特に、文字コードの変換(EBCDICからASCII、あるいはShift-JIS等の混在環境)を伴うレガシー移行においては、`INDEX`や`SEARCH`が依存するバイト単位の比較(Logical Compare)が、文字コード体系の差異によって全く異なる結果を生むことを忘れてはならない。
メインフレーム上では問題なくヒットしていた特殊文字(半角カナや外字)が、Javaへの移行後に一文字もヒットせず、バッチ処理が無限ループまたはデータ不整合に陥る現象は、移行現場の「あるある」の典型例である。

—

4. アーキテクチャの視点:オープン系移行における設計指針

テックリードやシステムアーキテクトとして、PL/IからJava/C#へのマイグレーションを主導する立場にあるなら、この`INDEX`と`SEARCH`の性能特性の差をどのようにモダン言語へトランスレーションすべきか、明確な指針を持たなければならない。

1. 単一文字・部分文字列の検索には `indexOf()` を徹底する
PL/Iの `INDEX(str, sub)` は、Javaの `String.indexOf(sub)` にそのまま1対1でマッピングされる。JITコンパイラやJVMの最適化により、内部的にはSIMD命令や高度な最適化ルーチンが適用されるため、性能劣化の心配はほぼない。
2. 複数文字のOR検索には正規表現や専用のセットを利用する
PL/Iの `SEARCH(str, char_set)` は、Javaであれば `Pattern` や `Matcher`、あるいはApache Commons等の `StringUtils.indexOfAny(str, searchChars)` に相当する。これを自前で `for` ループを回して1文字ずつ比較するような稚拙なコードに変換してはならない。レガシーコードの「意味(セマンティクス)」を正確に読み解き、モダン言語側で最も洗練されたAPIを選択することがアーキテクトの腕の見せ所である。

—

結びに代えて

PL/Iの構文規則や関数は、一見すると古臭いレガシーの遺物に思えるかもしれない。しかし、その背後にあるコンパイラの挙動、ハードウェア(メインフレーム)の命令セットとの密接な結合、そして極限の効率を追求した設計思想は、現代のハイパフォーマンス・コンピューティングにおいても通じる深い教訓に満ちている。

「予約語がない」という自由度の高さの裏で、コンパイラとエンジニアが阿吽の呼吸で信頼性を保ち続けてきた基幹システムの世界。その重みを理解した上でオープン系への橋渡しを行うことこそが、真のレガシー移行スペシャリストの役割なのである。

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