PL/Iの「INDEX」関数、その深淵とパフォーマンスの罠を読み解く
メインフレームの現場で何十年と稼働し続けるバッチ処理。その心臓部で文字列操作を担う `INDEX` 関数は、一見すると地味な存在ですが、大規模データのバッチ処理においては「計算量」という魔物が潜む要注意ポイントでもあります。
今回は、若手エンジニアがつい見落としがちな `INDEX` 関数の挙動と、実務でのコーディング最適化について、現場の視点から紐解いていきましょう。
—
1. INDEX関数の基本構造と「空文字」という特異点
PL/Iの `INDEX(SOURCE, SEARCH)` 関数は、`SOURCE` 文字列の中から `SEARCH` 文字列を検索し、最初に見つかった位置(1から始まるインデックス)を返します。見つからなければ `0` を返す――ここまではマニュアル通りです。
しかし、実務でトラブルの種になるのが「検索対象が空文字(”)だった場合」の挙動です。
なぜか返り値が「1」になる理由
PL/Iの仕様において、空文字を検索対象に指定すると、この関数は常に「1」を返します。これは「空の文字列は、あらゆる文字列の先頭に存在している」という数学的な定義に基づいた仕様です。
1
/ 実践的なコード例:INDEX関数の空文字挙動の検証 /
TEST_PROC: PROCEDURE OPTIONS(MAIN);
DCL SOURCE_STR CHAR(20) INIT(‘IBM_MAINFRAME’);
DCL EMPTY_STR CHAR(0) INIT(”);
DCL RESULT FIXED BIN(15);
/ 空文字を検索した場合の挙動チェック /
RESULT = INDEX(SOURCE_STR, EMPTY_STR);
/ ここでは RESULT は 1 になる。 /
/ IF文で判定する際は、必ず空文字チェックを先行させること /
IF RESULT = 1 THEN
PUT SKIP LIST(‘警告:INDEX関数が1を返しました’);
END TEST_PROC;
実務のバッチで「特定のフラグが立っていないデータを除外する」といったロジックを書く際、この「空文字だと1を返す」という仕様を失念していると、本来無視すべきデータまで誤って処理対象にしてしまうバグを生みます。「INDEXを使う前に必ずLENGTHで空チェックを入れる」。これが泥臭い現場で生き残るエンジニアの鉄則です。
—
2. パフォーマンス特性:計算量の意識
大規模なVSAMファイルやシーケンシャルデータセット(QSAM)を読み込むバッチでは、`INDEX` 関数をループ内で連発することは避けなければなりません。
線形探索のコスト
`INDEX` 関数は、内部的には文字列を1文字ずつスキャンする線形探索アルゴリズムを採用しています。計算量は `O(N M)`(N: ソースの長さ、M: 検索文字列の長さ)です。
数万件のレコードを処理する際、各レコードに対して複雑な文字列検索を行うと、CPU時間は指数関数的に増大します。特に、検索対象の文字列が固定長かつ長い場合、コンパイラが最適化を行ってくれるとはいえ、限界があります。
パフォーマンス改善のヒント
- ONユニットを活用した例外処理: 文字列検索の結果、異常なパターンが見つかった場合は、安易にフラグで判断せず、`SIGNAL CONDITION` でONユニットへ飛ばして処理を分離する方が、メインロジックの可読性と保守性が向上します。
- ハードコーディングの回避: 検索対象は可能な限り定数(DCL文でのSTATIC属性付与)として定義し、メモリ上の固定アドレスを参照させることで、微々たるものですがオーバーヘッドを削減できます。
—
3. 実践:VSAMレコード処理における定石
以下のコード例は、VSAMから読み込んだレコードに対して、特定のキーワードを検索する際の標準的な実装パターンです。
1
SEARCH_BATCH: PROCEDURE OPTIONS(MAIN);
DCL REC_BUFFER CHAR(500); / レコードバッファ /
DCL KEYWORD CHAR(10) INIT(‘ERROR_LOG’);
DCL POS FIXED BIN(15);
/ VSAM読み込みループのイメージ /
DO WHILE (NOT_EOF);
READ FILE(VSAM_FILE) INTO(REC_BUFFER);
/ 文字列検索前の事前チェック:これがパフォーマンスの鍵 /
IF LENGTH(TRIM(REC_BUFFER)) > 0 THEN DO;
POS = INDEX(REC_BUFFER, KEYWORD);
IF POS > 0 THEN DO;
/ 見つかった場合の処理をここに記述 /
CALL HANDLE_ERROR(POS);
END;
END;
END;
/ 予期せぬエラーはONユニットで捕捉する /
ON ERROR BEGIN;
PUT SKIP LIST(‘システムエラーが発生しました。ログを確認してください。’);
STOP;
END;
END SEARCH_BATCH;
—
結びに代えて
PL/Iは古臭い言語だと言われることもありますが、その厳格なデータ制御とメインフレームの強力なI/O性能が組み合わさったとき、他のモダン言語にはない圧倒的な安定感を発揮します。
`INDEX` 関数一つとっても、その裏側にある「空文字の仕様」や「計算量」を理解しているか否かで、障害発生時の対応速度や、バッチウィンドウに収めるための最適化の質が大きく変わります。
「とりあえず動くコード」から「保守しやすく、かつ計算資源を無駄にしないコード」へ。皆さんが手がけるそのバッチプログラムが、次の10年も安心して動かせるコードであることを願っています。何か詰まったら、またいつでも相談してください。現場からは以上です。
