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

おい、最近配属された後輩の面倒を見ていると、「PL/IってC言語やJavaと似てるから余裕っすよ」なんて軽く考えている奴が多い。特に文字列探索のあたりで、適当に`INDEX`関数を連発して夜間バッチのSOT(Start of Task)をぶっ飛ばす事故が後を絶たないんだ。

メインフレームの現場で生き残るには、言語の表面的な書き方だけ知ってちゃダメだ。コンパイラが裏でどんな機械語(アセンブラ)を吐いているか、CPUのサイクルをどう削るか、そこまで想像力を働かせなきゃいけない。

今日はな、PL/Iの文字列探索の二大巨頭、`INDEX`関数と`SEARCH`関数の性能比較について、アセンブラレベルの挙動から実務での使い分けまで徹底的に叩き込んでやる。心して聞けよ。

—

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

本題に入る前に、PL/Iという言語のユニークな背景を少しだけおさらいしておこう。
C言語やCOBOLと違って、PL/Iには厳密な意味での「予約語(Reserved Words)」が存在しない。`INDEX`も`SEARCH`も、そして `IF` や `DO` すらも、コンテキストによってはただの変数名として定義できてしまう。

1
/ こんな変態的なコードも(やろうと思えば)コンパイル通ってしまう /
DECLARE INDEX FIXED BINARY(31);
DECLARE SEARCH CHARACTER(10);

コンパイラは、前後の文脈や組み込み関数(BUILTIN)宣言を見て、「あ、これは文字列を検索する方の `INDEX` だな」と判断している。だからこそ、実務のソースコードを解析する時は、`DCL (INDEX, SEARCH) BUILTIN;` の宣言漏れや、名前の衝突による思わぬコンパイルエラー(あるいはもっとタチの悪いサイレントバグ)に細心の注意を払う必要があるんだ。この「何でもあり」の懐の深さが、レガシーシステムのブラックボックス化を加速させる原因でもあるんだけどな。

—

2. INDEX関数 vs SEARCH関数:アセンブラレベルの決定的な違い

さて、本日のメインテーマだ。
お前らは文字列中から特定の文字やパターンを探すとき、何も考えずに `INDEX` を使っていないか?

  • `INDEX(source, target)`: `source` の中から最初に `target` が出現する位置(インデックス)を返す。
  • `SEARCH(source, target)`: `source` の中から、第2引数 `target`(文字集合)に含まれる文字が最初に一致する位置を返す。あるいは、第3引数に文字を与えて「一致しない文字」を探すこともできる。

一見すると似たような関数だが、IBM Enterprise PL/I コンパイラが吐き出すアセンブラコード(マシン語)を覗いてみると、その振る舞いは全く異なる。

`INDEX` 関数の裏側:ハードウェア命令の直叩き

`INDEX` は、探す対象が基本的に「固定長の文字列(または部分文字列)」だ。コンパイラはこれを最適化し、z/Architectureの強力な文字比較命令、例えば `CLC`(Compare Logical Character) や `SRST`(Search String) 命令へとダイレクトに展開する。
特に `SRST` 命令は、レジスタに開始アドレスと終了アドレス、そして検索文字の条件を指定するだけで、汎用レジスタとCPUのパイプラインをフル回転させて高速にメモリをスキャンしてくれる。ハードウェア支援をモロに受けるから、処理速度は爆速だ。

`SEARCH` 関数の裏側:テーブル駆動とループ展開

一方で `SEARCH` は、第2引数に「複数の文字の集まり(文字集合)」をとる。例えば「英字のいずれか」や「特殊文字のどれか」といった、単一のパターンではない条件を処理するため、単純な `SRST` 命令一発ではカタが着かない。
コンパイラは内部的に、指定された文字集合を判定するための256バイトのビットマップテーブル(または条件分岐のツリー)を生成し、ループ処理を展開するか、あるいは汎用の比較ルーチンを呼び出す。
そのため、単純な「特定の一文字や文字列」を探す目的で `SEARCH` を使うと、無駄なテーブル参照やループオーバーヘッドが発生し、`INDEX` に比べてパフォーマンスがガタ落ちする。

—

3. 実務で使える!VSAMレコード処理と文字列探索のコード例

百聞は一見に如かず。実際のバッチ処理でVSAM(KSDS)のレコードを読み込み、フィールド内の特定文字を効率的に探すサンプルコードを見せてやろう。大文字ベース、適切なインデント、そして `BUILTIN` 宣言を徹底した、そのまま本番に投入できるクオリティのコードだ。

1
/ ================================================================= /
/ PROGRAM-ID: STRSRV01 /
/ REMARKS : VSAMレコードからINDEXとSEARCHを使い分けて効率的に探索する /
/ ================================================================= /
STRSRV01: PROC OPTIONS(MAIN);

/ 組み込み関数の明示的宣言(保守性のために必須) /
DCL (INDEX, SEARCH, LENGTH, TRIM) BUILTIN;

/ VSAM入力ファイル定義 /
DCL IN_FILE FILE RECORD INPUT
ENVIRONMENT(BUFNO(10) SIS);

/ レコードワークエリア /
DCL 1 VSAM_RECORD,
3 REC_KEY PIC'(8)9′, / 8桁のキー /
3 REC_DATA CHAR(400); / 400バイトのデータ部 /

DCL EOF_FLAG CHAR(1) VALUE(‘OFF’);
DCL POS FIXED BIN(31,0);
DCL ERR_COUNT FIXED BIN(31,0) INIT(0);

/ ONユニットによる入出力例外(ファイル終端など)の制御フロー /
ON ENDFILE(IN_FILE)
BEGIN;
EOF_FLAG = ‘ON’;
END;

ON ERROR
BEGIN;
PUT SKIP LIST(‘ 予期せぬエラーが発生しました。異常終了します。 ‘);
/ 必要に応じてここでログ出力やダンプ要求を記述 /
CLOSE FILE(IN_FILE);
SIGNAL FINISH;
END;

/ 処理開始 /
PUT SKIP LIST(‘=== 顧客データ文字列探索バッチ 処理開始 ===’);
OPEN FILE(IN_FILE);

READ FILE(IN_FILE) INTO(VSAM_RECORD);

DO WHILE (EOF_FLAG = ‘OFF’);

/ ————————————————————- /
/ ケース1: 単一の固定文字列(例: 区切り文字 ‘,’)を探す場合 /
/ -> ハードウェア命令を活かすため、必ず INDEX 関数を使うこと! /
/ ————————————————————- /
POS = INDEX(REC_DATA, ‘,’);

IF POS > 0 THEN DO;
/ カンマが見つかった場合の処理 /
/ 実務ではここでSUBSTRで切り出したりする /
END;

/ ————————————————————- /
/ ケース2: 複数文字のいずれか(例: 制御文字やスペース・タブ)を探す /
/ -> 文字集合から探すため SEARCH 関数を選択する /
/ ————————————————————- /
/ 例として、データ内にタブ(X’05’)または改行(X’15’)が含まれるか探索 /
POS = SEARCH(REC_DATA, ‘ ‘ || X’05’ || X’15’);

IF POS > 0 THEN DO;
/ 不正な制御文字が含まれている場合の例外処理 /
ERR_COUNT = ERR_COUNT + 1;
END;

/ 次のレコード読み込み /
READ FILE(IN_FILE) INTO(VSAM_RECORD);
END;

CLOSE FILE(IN_FILE);
PUT SKIP LIST(‘=== 処理正常終了 検出エラー数:’, ERR_COUNT, ‘ ===’);

END STRSRV01;

—

4. シニアアーキテクトからの実務アドバイス

このコードを見て、「なーんだ、どっち使っても動くじゃん」と思ったそこのお前。甘い。
数百万件、数千万件のレコードを処理する基幹系バッチにおいて、この選択のミスがどれだけのCPU時間を無駄にするか分かってるか?

1. 「単一文字・部分文字列の検索」には絶対に `INDEX` を使え。
ループの中や、巨大な文字列配列の走査で `SEARCH(str, ‘A’)` なんて書いたら、コンパイル時に余計なマスク生成コードを噛むハメになり、CPU使用量(秒数)がハネ上がる。メインフレームのMIPS課金の世界では、これがそのまま直結してインフラコストの増大になるんだ。
2. `BUILTIN` 宣言はサボるな。
もし宣言を忘れて独自関数と名前がバッティングしたり、コンパイラのデフォルト最適化が外れたりすると、それだけで数十倍のパフォーマンス劣化を招くことがある。「動けばいいや」ではなく、「コンパイラに最も効率的な機械語を吐かせるためのコード」を書くのが、プロのメインフレームエンジニアだ。
3. ONユニットと制御フローの原則
ファイル入出力のエラーやデータ異常に対する `ON` ユニットのスコープは明確に保て。例外発生時のフォールバック処理を間違えると、無限ループや夜間バッチ全体のデッドロックを引き起こす。

PL/Iは古い言語かもしれないが、マシンのアーキテクチャと対話しながら極限まで性能を引き出せる、実に奥が深い言語だ。今日の話を胸に刻んで、次のバッチ改修では無駄のない美しいコードを書いてくれよ。期待してるぞ。

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