こんにちは、後輩くん。
今日も夜間バッチのABEND(アベント)解析に追われているようだな。どれ、ちょっとコーヒーでも飲みながら、今日の本題に入ろうか。
お前が今取り組んでいるVSAMファイルの項目追加改修、順調かい?「なぜかレコード長が想定とズレる」「なぜか本番機だけCPU時間が跳ね上がる」――そんな不具合を踏んで、このブログに辿り着いたとしたら、お前の直感は正しい。今日のテーマは、PL/Iにおける「アライメント属性(ALIGNED / UNALIGNED)」だ。
メインフレームの心臓部であるSystem/390やz/Architectureの世界では、メモリの読み書きの効率はシステム全体のパフォーマンスに直結する。特に固定小数点数(FIXED BINARY / DECIMAL)を扱う際、このアライメントを理解していないと、パフォーマンスの劣化だけでなく、外部インターフェースでの致命的なデータズレを引き起こすことになる。
今日は、ベテランの私から、このアライメントの裏側と実務での処方箋を徹底的に叩き込んでやろう。
—
1. アライメント(ALIGNED / UNALIGNED)とは何か?
まずは基本のおさらいだ。PL/Iで変数を定義するとき、コンパイラはメモリ上のどこにその変数を配置するかを決める。
- ALIGNED(整列):
ハードウェア(CPU)が最も効率よくアクセスできるメモリ境界(2バイト境界、4バイト境界、8バイト境界など)にデータを強制的に合わせる属性だ。例えば、4バイトのフルワード境界なら、メモリアドレスが4の倍数の位置に配置される。
- UNALIGNED(非整列):
境界の制約を無視して、前のデータの直後に隙間なく(パディングなしで)データを詰めて配置する属性だ。
デフォルト挙動の罠
PL/Iでは、特に指定しない場合のデフォルト属性がデータ型やコンパイラのオプションによって異なるため注意が必要だ。
一般的に、スカラーのFIXED BINARYやFIXED DECIMALの多くは、デフォルトで `ALIGNED` になることが多い。しかし、構造体(STRUCTURE)の中では、要素のデータ型やコンパイラのデフォルト設定によって `UNALIGNED` が適用されることもある。これが「思わぬメモリの隙間(パディング)」を生む元凶となる。
—
2. ストレージ効率 vs アクセス速度のトレードオフ
現場でよくある議論が、「ストレージを節約するか、CPU時間を節約するか」だ。
ストレージ効率(UNALIGNEDの勝利)
VSAMのKSDSやRRDS、あるいは外部磁気テープに書き出すレコードレイアウトを考えてみてほしい。COBOLのCOPY句から自動生成されたような外部定義構造体をPL/Iで受ける場合、1バイトのズレも許されないことが多い。
`ALIGNED` だと、2バイトや4バイトの境界に合わせるために、メモリの「パディング(隙間)」が自動挿入される。これがレコード全体で積み重なると、ファイルサイズが膨れ上がり、I/Oの負荷が増大する。
そのため、外部ファイルや通信バッファとやり取りするレコード構造体は、原則として `UNALIGNED` で統一するのがメインフレーム開発の鉄則だ。
アクセス速度(ALIGNEDの勝利)
一方で、CPUのアーキテクチャは、奇数番地や境界をまたいだデータアクセス(アン・アラインド・アクセス)を嫌う。ALIGNEDなデータであれば1回のマシン命令でロードできるものが、UNALIGNEDだと複数回のメモリアクセスやシフト演算が必要になり、結果としてCPU時間が跳ね上がる。
内部でゴリゴリ計算を回すワークエリア内の変数であれば `ALIGNED` にしておくべきだ。
—
3. レコード入出力・VSAMアクセスにおける実践的注意点
マイグレーションや他システムとの連携で最も事故りやすいのが、このアライメントのミスマッチだ。
例えば、COBOLで書かれた既存の外部ファイル定義(COMP-3やBINARYデータが隙間なく並んでいる)を、PL/Iの構造体でそのまま受け取るとしよう。この時、PL/I側で `ALIGNED` が暗黙的に適用されてしまうと、コンパイラが勝手にパディングバイトを挿入し、COBOL側が期待するオフセット位置と完全にズレてしまう。
「フィールドAの後になぜかゴミデータが入る」「数値項目でDATAEXCEPTION(S0C7)が頻発する」
大抵の原因はこれだ。外部インターフェースを持つ構造体や、再定義(DEFINEDやUNSPEC)を行うデータ領域では、構造体のレベルで `UNALIGNED` を明示的に指定するか、プロパイラのコンパイルオプション(DEFAULTなど)で制御することが極めて重要になる。
—
4. 実践コード:アライメントの違いによるレイアウトと処理の制御
百聞は一見にしかずだ。実際のPL/Iコードを見てみよう。
以下のサンプルは、外部VSAMファイルに書き出すレコードレイアウトを想定し、構造体全体に `UNALIGNED` を指定した例と、それに伴うエラーハンドリング(ONユニット)の組み込み方を示した実践的なコードだ。
//
/ プログラム名: MIGRSR01 /
/ 概要: VSAMレコードの入出力とアライメント制御のサンプル /
//
MIGRSR01: PROC OPTIONS(MAIN);
/ 外部VSAMレコード定義:ストレージ効率とレイアウト一致のため /
/ UNALIGNED属性を明示的に付与する /
DECLARE 1 VSAM_REC ALIGNED, / レコード全体基準 /
5 REC_HEADER UNALIGNED,
10 TRAN_CODE CHAR(4), / トランザクションコード /
10 TRAN_DATE FIXED DEC(7,0), / 伝票日付 (YYMMDD+) /
5 REC_BODY UNALIGNED,
10 CUST_ID FIXED BIN(31,0), / 顧客ID (フルワード) /
10 TRAN_AMT FIXED DEC(11,2); / 取引金額 /
/ 内部計算用ワークエリア:アクセス速度優先のためALIGNED /
DECLARE W_TOTAL_AMT FIXED DEC(13,2) ALIGNED INIT(0);
/ ファイル定義 /
DECLARE OUT_FILE FILE RECORD OUTPUT
ENVIRONMENT(FB BLKSIZE(800) LRECL(26));
/ — ONユニットによる例外監視 — /
ON CONVERSION
BEGIN;
DISPLAY(‘【警告】数値変換エラーが発生しました。データをスキップします。’);
/ 異常データに対するフェイルセーフ処理 /
TRAN_AMT = 0;
GOTO ERROR_SKIP;
END;
OPEN FILE(OUT_FILE) OUTPUT;
/ ダミーデータのセット(実際にはDBや別ファイルから読み込む) /
TRAN_CODE = ‘TR01’;
TRAN_DATE = 20231025;
CUST_ID = 12345678;
TRAN_AMT = 150000.50;
/ ビルトイン関数を用いたデータ検証 /
IF ^VALID(TRAN_AMT) THEN DO;
DISPLAY(‘不正なパック十進数が検出されました: ‘ || UNSPEC(TRAN_AMT));
SIGNAL CONVERSION;
END;
/ 内部ワークでの計算(ALIGNEDの効果で高速に処理) /
W_TOTAL_AMT = W_TOTAL_AMT + TRAN_AMT;
/ レコード出力 /
WRITE FILE(OUT_FILE) FROM(VSAM_REC);
ERROR_SKIP:
CLOSE FILE(OUT_FILE);
DISPLAY(‘処理が正常に終了しました。総額: ‘ || CHAR(W_TOTAL_AMT));
RETURN;
END MIGRSR01;
コードの解説とベテランの急所指摘
1. 構造体レベルでの制御:
`VSAM_REC` 自体は `ALIGNED` ですが、その配下の `REC_HEADER` や `REC_BODY` に対して `UNALIGNED` を指定することで、外部ファイルとのレイアウト互換性を完全に保ちつつ、無駄なパディングを防いでいます。
2. VALIDビルトイン関数:
外部から読み込んだデータや、アライメントの不一致・領域破壊で壊れた可能性のある `FIXED DECIMAL`(パック十進数)を処理する前には、必ず `VALID` 関数を挟みなさい。これを怠ると、現場で一番恐れられるS0C7(Data Exception)の餌食になる。
3. ON CONVERSIONユニット:
万が一データが破損していて数値変換に失敗した場合でも、システムが即座に異常終了(ABEND)するのを防ぎ、ログを吐いて安全にバイパスするための基本テクニックだ。基幹バッチでは必須の作法となる。
—
5. おわりに:トラブルを防ぐためのコーディング標準
後輩くん、アライメント属性の指定を「コンパイラに任せておけばいいや」と軽視していなかったか?
小規模なプログラムなら動くかもしれないが、何百万件もレコードを処理する基幹バッチや、他システムとバイナリデータを直接やり取りするインターフェースプログラムにおいて、この「たった数バイトのズレ」が数日間の原因不明の調査地獄を生む原因になる。
原則として、以下のルールをチームのコーディング規約に叩き込んでおけ。
- 外部ファイル・通信電文・DCLGEN(DB2ホスト変数構造体など)のマップ: 原則 `UNALIGNED` を意識し、COBOLや外部仕様書とのオフセット一致をCHECKマンシームで確認する。
- 純粋な内部計算用変数: パフォーマンスを最大化するため `ALIGNED` をデフォルトとする。
地味な属性一つだが、これを制する者がPL/Iのメモリ管理を制す。
今夜のバッチ解析が終わったら、自分が担当しているプログラムの構造体定義をもう一度見直してみなさい。
それじゃあ、仕事に戻るとしようか。健闘を祈る!
