【テクニカル・上級編】INDEX関数による検索アルゴリズムとパフォーマンス – PL/Iの基本構文とデータ制御実践ガイド

メインフレームの深淵:INDEX関数と動的メモリ操作が織りなす「枯れた」最適化の極意

メインフレームの世界で数十年、数多の基幹システムが「スパゲッティ」と揶揄されるのを横目に、私は常にPL/Iという言語の冷徹なまでの合理性に魅了されてきました。特に、文字列検索の基本である`INDEX`関数。若手エンジニアは「ただの検索関数だろう」と高を括りますが、この関数の背後にあるコンパイラの挙動とメモリアクセスの特性を理解しているか否かで、数百万件のレコードを処理するバッチのランタイムは劇的に変わります。

今日は、JavaやC#へのマイグレーションを控えたアーキテクトが、現行のPL/I資産を「正しく」解釈し、最適化するための知見を共有します。

1. INDEX関数の内部挙動と「0」の哲学

`INDEX(string, pattern)`は、文字列`string`から`pattern`が最初に出現する位置を返します。見つからなければ「0」を返す。この戻り値が0であるという事実は、単なる論理値の判定にとどまりません。

/i
/ 大規模な文字列検索における典型的なパターン /
DCL TARGET_STR CHAR(4096) VAR;
DCL SEARCH_KEY CHAR(32) VAR;
DCL POS FIXED BIN(31);

/ 0以外の判定は、コンパイラが比較命令として最適化しやすい /
POS = INDEX(TARGET_STR, SEARCH_KEY);
IF POS > 0 THEN DO;
/ 発見時の処理:ここでオフセット計算を行う /
CALL PROCESS_DATA(SUBSTR(TARGET_STR, POS));
END;

ここで重要なのは、コンパイラが生成する機械語コードです。`INDEX`は内部的にハードウェアレベルの命令(例えばIBM Zの`TRT` – Translate and Test命令など)を活用するよう最適化されています。しかし、対象文字列が数メガバイトに及ぶ場合、ループ内で繰り返し呼び出すと、キャッシュミスによるオーバーヘッドが無視できません。

アーキテクトの視点:
マイグレーション先がJavaであれば`String.indexOf`が使われますが、メインフレームでは「検索範囲を動的にポインタで操作する」というアプローチが取れます。`ADDR`と`BASED`変数を用いたバッファの直接参照は、PL/Iの真骨頂です。

2. ポインタとBASED変数による動的メモリ最適化

大規模なバッチ処理で、いちいち`SUBSTR`関数で文字列をコピーしていては、CPUサイクルを浪費するだけです。`BASED`変数を用いて、メモリ上の特定オフセットを「マッピング」する手法を推奨します。

/i
/ ポインタを用いた動的検索の効率化手法 /
DCL BUFFER_PTR POINTER;
DCL BUFFER_AREA CHAR(32767) BASED(BUFFER_PTR);

/ メモリ上の特定位置を直接参照し、無駄なデータ転送を防ぐ /
BUFFER_PTR = ADDR(RAW_DATA_AREA) + (CURRENT_OFFSET – 1);

IF INDEX(BUFFER_AREA, SEARCH_KEY) > 0 THEN DO;
/ ここでポインタをずらすことで、コピー負荷ゼロの検索が可能 /
END;

この手法は、CICSオンライン処理における膨大な共用メモリ(TDQやTSQ)の解析時にも威力を発揮します。ダンプ解析時に、`BASED`変数のオフセットがずれていることによるABEND(S0C4など)に遭遇した経験があるなら、このメモリレイアウトの重要性は骨身に染みているはずです。

3. 現場で嵌まる「地雷」:パックデシマルの符号とSQL

マイグレーション時に必ずと言っていいほど問題になるのが、`PIC S9(n) COMP-3`で定義されたパックデシマルデータの扱いと、`INDEX`による検索結果の整合性です。

  • パックデシマルの符号反転バグ:

EBCDICコード体系特有の符号ビット処理(`x’C’`=正、`x’D’`=負、`x’F’`=符号なし)を、移行先のバイナリ形式と正しくマッピングできないと、計算結果が狂います。特にSQL経由で取り出した値を`INDEX`で検索する場合、DB2の型変換(CAST)の影響で意図しないパディングが発生することがあります。

  • 埋め込みSQLのエッジケース:

`WHERE`句で`INDEX`的な処理をSQLに委譲する場合、DB2のインデックス使用効率を考慮しなければなりません。PL/I側で`INDEX`を叩くのか、DB2側で`LIKE`演算子を使うのか。この判断基準は、データ量と「バッファプールのヒット率」に帰結します。

4. ダンプ解析と移行設計の心得

万が一のABEND時には、SYSUDUMPを読み解く力が必要です。`INDEX`関数付近で落ちている場合、それは多くの場合「検索対象の長さが0である」か「ポインタが保護領域を跨いでいる」かのどちらかです。

移行プロジェクトのアーキテクトには、以下のスタンスを求めたい。

1. 「現状のコードをそのまま移植するな」: コンパイラが最適化していた低レイヤーの挙動を、Java/C#のランタイム環境でどう再現するか。あるいは、むしろ高レイヤーのライブラリに任せるべきか。
2. 「性能要件はCPU時間ではなくスループットで測れ」: メインフレームのMIPS単価と、クラウドのインスタンスコストを比較する際、PL/Iのループ処理を単なる`for`ループに書き換えるだけでは、コスト増大を招きます。

PL/Iは、ハードウェアを直接叩くための洗練されたインターフェースです。この言語を「レガシー」として切り捨てる前に、その背後にある「なぜこのような命令セットになっているのか」という設計思想を深く理解してください。それが、複雑な基幹システムを次世代へ安全に受け渡すための、唯一にして最大の近道なのです。

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