おい、若いの。今日も今日とて、ずらりと並ぶJCLのログや、何百万ステップもある巨大なPL/Iバッチプログラムの海原に溺れていないか?
メインフレームの世界に入ると、COBOLの単調な手続き型記述とは一味違う、PL/Iの圧倒的な表現力と自由度の高さに最初は面食らうものだ。そして、その自由度の裏側にある「ハードウェアの都合」を生々しく突きつけられる瞬間がやってくる。そう、今回取り上げる「構造体アライメント(ALIGNED / UNALIGNED)」だ。
基幹システムのオンライン応答性能のチューニングや、日夜巨大なVSAMファイルを激しく叩き続けるバッチのI/O効率改善を任されたとき、このアライメントの概念を理解しているかどうかで、君のエンジニアとしての格が一段も二段も変わってくる。さあ、しっかりとついてきなさい。
—
1. なぜメインフレームの構造体に「隙間(パディング)」が生まれるのか?
まずは、ハードウェアがメモリをどう読んでいるかという、泥臭い物理の話から始めよう。
System z(現在のIBM Z)などの強力なCPUは、メモリからデータを読み書きする際、4バイトや8バイトといった「境界(ワード境界やダブルワード境界)」を基準にアクセスしたほうが圧倒的に効率が良い。奇数番地から無理やり2バイトや4バイトの数値を取り出そうとすると、CPU内部で追加のメモリアクセスサイクルが発生したり、最悪の場合はアライメント例外でバッチが壮大にアベンド(異常終了)したりする。
このハードウェアの特性に対応するため、PL/Iコンパイラはデフォルトで「ALIGNED(アライメント)」というルールを適用する。
これは、「各データ型はその型に応じた境界の倍数のメモリアドレスに配置する」という原則だ。結果として、メモリの番地をきれいに合わせるために、データとデータの間に「パディング(意味のない空白バイト)」が勝手に挿入される。
一方で、メモリ容量や外部記憶装置(VSAMやQSAM)のレコード長、あるいは外部の他システム(C言語やCOBOLのレコードレイアウト)との厳密なインターフェース電文のサイズを1バイトたりとも狂わせたくない場合がある。ここで登場するのが、パディングを一切許さない「UNALIGNED(アンアライメント)」属性だ。
—
2. ALIGNED と UNALIGNED の決定的な違い
百聞は一見に如かず。まずは以下の比較表を見て頭に叩き込みなさい。
| 項目 | ALIGNED 属性(デフォルト) | UNALIGNED 属性 |
| :— | :— | :— |
| メモリ配置 | 各データ型の境界(2, 4, 8バイト等)に強制配置 | バイト単位で隙間なく連続配置 |
| CPUアクセス速度 | 最速(ハードウェアの最適境界に一致) | やや低下(境界を跨ぐアクセスが発生し得る) |
| メモリ/ストレージ効率 | 非効率(パディングによるデッドスペースが発生) | 最高(1バイトの無駄もない) |
|主な用途 | 内部で酷使するワークエリア、高頻度計算用構造体 | VSAM/QSAMレコード、外部電文、ファイルI/Oレイアウト |
現場で最も多いトラブルが、「VSAMファイルへの書き出し用構造体をデフォルト(ALIGNED)のまま定義してしまい、レコード長が想定より膨らみ、後続のCOBOLプログラムや別システムが読み込んだ際に項目の位置が見事にズレて大惨事になる」というパターンだ。笑い事じゃないぞ、本当によくあるんだからな。
—
3. 実践!PL/Iコードでみるアライメントの挙動とVSAMアクセス
それでは、実際のPL/Iソースコードを通じて、この挙動を体感してもらおう。
以下のコードは、顧客マスタのVSAM(KSDS)を読み込み、内部で加工して別のレコードとして出力する典型的なバッチプログラムの断片だ。大文字ベースの記述、適切なインデント、そして組み込み関数(BUILTIN)の活用に注目してほしい。
STG_DEMO: PROC OPTIONS(MAIN);
/————————————————————–/
/ 顧客レコード構造体定義(外部ファイル入出力用:UNALIGNED指定) /
/————————————————————–/
DCL 1 CUST_REC_EXT UNALIGNED,
5 CUST_ID CHAR(5), / 顧客ID /
5 CUST_TYPE FIXED BIN(15),/ 顧客種別(2バイト) /
5 CUST_BALANCE FIXED DEC(9,2);/ 残高(5バイトパック)/
/————————————————————–/
/ 内部演算用ワーク構造体(CPU処理効率重視:ALIGNED指定) /
/————————————————————–/
DCL 1 CUST_REC_INT ALIGNED,
5 CUST_ID CHAR(5),
5 CUST_TYPE FIXED BIN(15),
5 CUST_BALANCE FIXED DEC(9,2);
/ ファイル定義 /
DCL CUSTFILE FILE RECORD INPUT ENVIRONMENT(FB RECSIZE(12));
DCL OUTFILE FILE RECORD OUTPUT ENVIRONMENT(FB RECSIZE(12));
DCL IO_EOF BIT(1) INIT(‘0’B);
/ 終了タスク用ONユニット(例外処理) /
ON ENDFILE(CUSTFILE) IO_EOF = ‘1’B;
OPEN FILE(CUSTFILE) INPUT, FILE(OUTFILE) OUTPUT;
/ メイン処理ループ /
READ FILE(CUSTFILE) INTO(CUST_REC_EXT);
DO WHILE(^IO_EOF);
/ 外部形式(UNALIGNED)から内部演算用(ALIGNED)へ転記 /
CUST_REC_INT.CUST_ID = CUST_REC_EXT.CUST_ID;
CUST_REC_INT.CUST_TYPE = CUST_REC_EXT.CUST_TYPE;
CUST_REC_INT.CUST_BALANCE = CUST_REC_EXT.CUST_BALANCE;
/ 高速なCPU演算を想定したビジネスロジック /
IF CUST_REC_INT.CUST_TYPE = 1 THEN
CUST_REC_INT.CUST_BALANCE = CUST_REC_INT.CUST_BALANCE 1.08;
/ 外部出力形式へ再び詰め替える /
CUST_REC_EXT.CUST_ID = CUST_REC_INT.CUST_ID;
CUST_REC_EXT.CUST_TYPE = CUST_REC_INT.CUST_TYPE;
CUST_REC_EXT.CUST_BALANCE = CUST_REC_INT.CUST_BALANCE;
/ VSAM/QSAMファイルへの書き出し(LENGTH組み込み関数で厳密に制御) /
WRITE FILE(OUTFILE) FROM(CUST_REC_EXT) LENGTH(STG(CUST_REC_EXT));
READ FILE(CUSTFILE) INTO(CUST_REC_EXT);
END;
CLOSE FILE(CUSTFILE), FILE(OUTFILE);
END STG_DEMO;
コードの解説と現場の知見
1. レコード入出力と `UNALIGNED` の鉄則
`CUST_REC_EXT` では明確に `UNALIGNED` を宣言している。これにより、`CHAR(5)`(5バイト)の直後に `FIXED BIN(15)`(2バイト)が隙間なく続き、合計長が正確に保証される。もしこれを `ALIGNED` にすると、コンパイラの種類や構造体のネストによっては、意図しないパディングバイトが挟まり、ファイル定義(F/FBのレコード長)との不一致を引き起こす原因になる。
2. ストレージ長を取得する `STG` 組み込み関数
コードの最後にある `LENGTH(STG(CUST_REC_EXT))` に注目してくれ。PL/Iでは `STG`(STORAGEの略)組み込み関数を使うことで、その変数がメモリ上で実際に占有しているバイト数を動的・静的に正確に取得できる。レコード長が固定でない拡張構造体などを扱う際には、この `STG` 関数の使いこなしがバグを防ぐ鍵となる。
3. 内部処理用と外部入出力用の構造体分離
大規模なバッチの設計では、このように「ファイルIO用(UNALIGNED)」と「CPUでの演算用(ALIGNED)」の構造体を明確に分けるのが、プロフェッショナルなアーキテクトのやり方だ。I/O時は安全にバイト列をパッキングし、メモリ上に展開した後は `ALIGNED` のワークエリアで最速の演算を行う。この一手間が、数千万件を処理するバッチの実行時間を何分も縮めることにつながるのだ。
—
4. デバッグの現場から:アライメント起因のトラブルシューティング
最後に、現場で泣く泣くデバッグに奔走した先輩からのアドバイスを送ろう。
- 「データ例外(S0C7など)」に遭遇したら疑うべきポイント
数値項目のアライメントが狂った状態で無理やり算術演算を行おうとすると、システム異常終了(S0C7: データ例外)を引き起こすことがある。特に、他の言語(COBOLやC)から渡ってきたポインタや領域をPL/Iのベースライズ(Based変数)でマッピングする際、アライメントの前提違いからフィールドのオフセットがズレ、ゴミデータを数値として読み込んでしまうケースが後を絶たない。
- コンパイラオプションの確認
IBMのPL/Iコンパイラには、デフォルトのアライメント挙動を制御するオプション(`ALIGN` / `NOALIGN`)が存在する。レガシーシステムの移行(マイグレーション)時に、古いコンパイラから最新の Enterprise PL/I へ載せ替えた際、デフォルトの解釈の微差で思わぬズレが生じることがあるため、コンパイルリストのクロスリファレンスを必ず自分の目で隅々まで確認する習慣をつけなさい。
構造体のアライメントは地味なテーマに見えるかもしれないが、メインフレームの足回りを支える極めて重要なアーキテクチャの根幹だ。メモリの効率とCPUの速度、この二律背反する要素のバランスをどう取るか。それをコントロールするのが、他でもないシステムアーキテクトである君の腕の見せ所だよ。
さて、理論はここまでだ。実際のコーディングに戻るとしよう。何か分からないことがあれば、いつでも私の席に来るといい。
