こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他の言語をバリバリ書いてきた方にとって、IBMメインフレームの世界、そしてそこで動く「PL/I(ピーエルワン)」という言語は、少し古めかしく、怖く感じられるかもしれません。「なんだか呪文みたいな書き方だな…」「メモリの細かい制御とか、難しそう…」なんて思っていませんか?
大丈夫ですよ、安心してください。一つずつ紐解けば、PL/Iは非常に論理的で、マシン性能を極限まで引き出せる洗練された言語なんです。
今回は、PL/Iにおける数値データの基本中の基本、`FIXED BINARY(15)` と `(31)` のメモリ配置とアライメント(境界調整)について、実務の現場で役立つコツを交えながら優しく解説していきますね。
—
そもそも `FIXED BINARY` ってなに?(COBOLやJavaとの違い)
他の言語を経験された方なら、「整数型」といえばピンとくるでしょう。
PL/Iで使われる `FIXED BINARY`(固定小数点二進数)は、まさにその整数型のことです。
- `FIXED BINARY(15)`: 15ビットの精度を持つ整数。マイナスも扱えるので、実際には符号を含めて16ビット(2バイト=ハーフワード)の領域を使います。Javaでいう `short` や、COBOLの `PIC S9(4) COMP` あたりに相当します。
- `FIXED BINARY(31)`: 31ビットの精度を持つ整数。符号を含めて32ビット(4バイト=フルワード)の領域を使います。Javaの `int` や、COBOLの `PIC S9(9) COMP` ですね。
「なるほど、15桁だから15バイトなんだね?」なんて勘違いしてしまいがちですが、括弧の中身は「ビット数(精度)」を表すのがPL/Iのお約束です。ここが最初のつまずきポイントですが、慣れれば怖くありません!
—
メモリの「アライメント」ってどういうこと?
さて、ここからが本題です。IBMメインフレーム(System z)のハードウェアは、メモリを読み書きするときに「キリの良いアドレス(境界)」からアクセスすることが大好きです。
- ハーフワード境界(2の倍数のアドレス): `FIXED BINARY(15)` が好む場所。
- フルワード境界(4の倍数のアドレス): `FIXED BINARY(31)` が好む場所。
もし、CPUが「本当は4の倍数のアドレスから読みたいのに、中途半端な場所にあるぞ?」となると、ハードウェアが追加のロード処理を行ったり、場合によっては性能がガクッと落ちてしまいます。最悪の場合、プログラムが異常終了(ABEND)してしまうことも……。
そのため、PL/Iのコンパイラは、CPUが最も効率よくデータを読み書きできるように、自動的にメモリの配置を調整してくれます。これがアライメント(境界調整)です。
—
構造体(STRUCT)で起こる「パディング(隙間)」の罠
単体の変数であればコンパイラが勝イイ感じに配置してくれますが、実務でよく使う構造体(PL/Iでは `DECLARE` で階層構造を作ります)を定義したとき、思わぬ罠が潜んでいます。
次のコード例を見てみてください。
1
DCL 1 EMPLOYEE_REC,
2 EMP_ID FIXED BIN(31), / 社員番号 (4バイト) /
2 EMP_CODE FIXED BIN(15), / 役職コード (2バイト) /
2 EMP_SALARY FIXED BIN(31); / 給与 (4バイト) /
一見すると、「4バイト + 2バイト + 4バイト = 合計10バイト」の構造体ができあがったように見えますよね?
しかし、メインフレームのメモリ上では、そう単純にはいきません。
実際にメモリで何が起きているのか?
1. `EMP_ID` は `FIXED BIN(31)` なので、4バイトのフルワード境界に配置されます。(オフセット 0〜3バイト目)
2. 次の `EMP_CODE` は `FIXED BIN(15)`(2バイト)です。そのまま直後の4バイト目から配置したくなりますよね?
3. しかし、その次にある `EMP_SALARY` は `FIXED BIN(31)` なので、「4の倍数のアドレス(フルワード境界)」から始まらなければなりません。
コンパイラは頭を使います。「あ、次にフルワードが来るから、`EMP_CODE` の後ろに中途半端な隙間があると困るな。よし、ここに2バイトの隙間(パディング)を挟んで、次のデータをキレイな4の倍数の位置に置こう!」
結果として、メモリの配置はこうなります:
- `EMP_ID` : 4バイト
- `EMP_CODE` : 2バイト
- [パディング(隙間)] : 自動挿入された無駄な2バイト
- `EMP_SALARY` : 4バイト
合計サイズは10バイトではなく、12バイトになるのです!
もしこの構造体を外部ファイル(VSAMや順編成ファイル)にそのまま書き出したり、CICSなどの通信領域(COMMAREA)でやり取りしたりする場合、他言語のプログラムや定義とサイズがズレてしまい、データ化けの原因になります。「あれ? データがずれて読み込まれるぞ…?」というバグの多くは、このパディングが原因だったりします。
—
パディングを防ぎ、安全にデータを扱うためのテクニック
では、このような予期せぬパディングを防ぎ、意図した通りのメモリレイアウトにするにはどうすればよいのでしょうか?
いくつか実務で使えるアプローチがあります。
1. 属性「ALIGNED」と「UNALIGNED」を使いこなす
PL/Iでは、変数や構造体の宣言時に、境界調整を行うかどうかを明示的に指定できます。
- `ALIGNED`: ハードウェアの境界に合わせる(デフォルト・高パフォーマンス)
- `UNALIGNED`: 境界を気にせず、隙間なく詰めて配置する(領域節約・外部連携向き)
先ほどの構造体を、ファイル入出力や他システム連携用に「隙間なく」定義したい場合は、次のように `UNALIGNED` を指定します。
1
DCL 1 EMPLOYEE_REC UNALIGNED, / 構造体全体を隙間なく詰める指定 /
2 EMP_ID FIXED BIN(31),
2 EMP_CODE FIXED BIN(15),
2 EMP_SALARY FIXED BIN(31);
こうすることで、パディングは一切挿入されず、きっちり10バイトのデータ構造になります。ただし、`UNALIGNED` なデータへのアクセスは、CPUが少し余計な処理をするため、数百万件を処理するようなバッチプログラムの計算用変数などでは `ALIGNED`(デフォルト)を選ぶのがパフォーマンスの観点から鉄則です。
- 計算をバリバリ行う内部ワークエリア = `ALIGNED` (パフォーマンス重視)
- ファイルや通信でやり取りするレコードレイアウト = `UNALIGNED` (サイズ・互換性重視)
この使い分けが、レガシーシステムのアーキテクトとしての腕の見所ですね!
—
財布の紐ならぬ「メモリの隙間」をきっちり管理するPL/Iの世界、少し身近に感じられたでしょうか?
最初は独特なルールに驚くかもしれませんが、ハードウェアの動きとダイレクトにつながっているこの感覚が分かってくると、メインフレームプログラミングはとても奥深く、エキサイティングなものになりますよ。
日々のマイグレーションや保守作業、応援しています。またいつでも気軽にこのブログを覗きに来てくださいね!
