PL/Iでバッチ処理のスピードアップ!BUFFERED属性の魔法を解き明かす
こんにちは!レガシーシステムの世界へようこそ。JavaやCOBOLには慣れ親しんでいるあなたも、IBMメインフレームのPL/Iにはちょっとドキドキしているかもしれませんね。でも、大丈夫!この広大なメインフレームの世界も、一つずつ紐解いていけば、きっとあなたの力強い味方になってくれますよ。
今回は、PL/Iでバッチ処理のパフォーマンスを劇的に向上させる、ちょっと魔法のような「BUFFERED属性」について、その仕組みと使い方を、イメージしやすい例えを交えながら、優しく解説していきます。
PL/Iプログラムの骨組み:PACKAGE、PROCEDURE、そしてOPTIONS(MAIN)
まず、PL/Iプログラムがどういう構造になっているのか、簡単におさらいしましょう。Javaでいうところのクラスやメソッド、COBOLでいうところのセクションや段落のようなものですね。
- PACKAGE: これは、関連するプログラムやデータ、手続きをひとまとめにする「箱」のようなものです。複数のプログラムを連携させたい時や、共通のデータ構造を定義したい時に使われます。
- PROCEDURE: これは、特定の処理を行う「手続き」や「関数」のようなものです。JavaのメソッドやCOBOLの段落に相当します。
- OPTIONS(MAIN): PROCEDUREにこのオプションが付くと、そのPROCEDUREがプログラムの「開始点」になります。つまり、プログラムを実行したときに、最初に動き出す部分のことです。Javaでいう `public static void main(String[] args)` のようなイメージですね。
これらはPL/Iプログラムの基本的な構造を形作っています。最初は少し戸惑うかもしれませんが、慣れてくると、プログラムのどこで何が行われているのかが掴みやすくなりますよ。
バッチ処理の「遅い!」を解決する秘密兵器:BUFFERED属性
さて、本題に入りましょう。バッチ処理で大量のデータを扱う場合、ファイルへの読み書きがボトルネックになること、よくありますよね?Javaで `BufferedReader` を使って読み込みを速くしたり、COBOLでレコード単位のI/Oを工夫したり、といった経験があるかもしれません。PL/Iにも、そんなパフォーマンス改善のための強力な味方があります。それが、ファイル定義で指定する `BUFFERED` 属性です。
UNBUFFERED属性:素直だけど、ちょっとお節介?
まずは、対照的な `UNBUFFERED` 属性から見てみましょう。
これは、プログラムがファイルを読み書きするたびに、直接ディスク(またはそれに相当するストレージ)とやり取りをする、という指示です。
イメージとしては、あなたが友達に手紙を出すときに、書いたらすぐにポストに投函するような感じです。
- メリット:
- プログラムが書いたデータは、すぐにディスクに書き込まれるので、万が一プログラムが途中で止まっても、書いたデータが失われるリスクは低いです。
- メモリをあまり消費しません。
- デメリット:
- I/O回数が多くなる: 1行書くたびに、ディスクにアクセスする。1文字読むたびに、ディスクにアクセスする。これが繰り返されると、ディスクアクセスは本来、CPUの処理に比べて非常に遅いので、全体の処理時間が長くなってしまいます。
- パフォーマンスの低下: 特に、大量のデータを扱うバッチ処理では、この「ちりつも」でパフォーマンスが著しく低下することがあります。
BUFFERED属性:賢くまとめて、効率アップ!
一方、`BUFFERED` 属性は、もっと賢く、効率的にファイルを扱います。
これは、プログラムがファイルを読み書きする際に、一度メインメモリ上にある「バッファ」と呼ばれる一時的な保管場所を経由させる、という指示です。
例えるなら、手紙を出すときに、すぐにポストに投函するのではなく、一旦机の上にまとめておいて、ある程度溜まってからまとめてポストに投函する、あるいは、郵便屋さんがまとめて集荷に来てくれる、というイメージです。
- メリット:
- I/O回数が激減: プログラムは、メモリ上のバッファにデータを書き込んだり、バッファからデータを読み込んだりします。ディスクへの実際のアクセスは、バッファがいっぱいになったり、空になったり、あるいはプログラムがファイルを閉じるときなど、必要なタイミングにまとめて行われます。
- パフォーマンスの劇的な向上: I/O回数が減ることで、ディスクアクセスの待ち時間が大幅に削減され、バッチ処理全体の実行時間が短縮されます。これは、大量のデータを扱うバッチ処理においては、まさに「魔法」のような効果を発揮します。
- CPU負荷の軽減: ディスクI/Oの待ち時間が減ることで、CPUが他の処理に使える時間が増え、システム全体の効率も上がります。
- デメリット:
- メモリ消費量が増加: バッファのために、ある程度のメモリを消費します。ただし、最近のメインフレームではメモリは潤沢に利用できる場合が多く、パフォーマンス向上とのトレードオフとして、このデメリットは許容できることが多いです。
- データ損失のリスク(限定的): プログラムが予期せず異常終了した場合、バッファに書き込まれたがまだディスクに書き出されていないデータは失われる可能性があります。しかし、これは通常、エラーハンドリングを適切に行うことで回避できます。
実際のPL/Iコードで見てみましょう!
では、実際のPL/Iコードで、`BUFFERED` 属性がどのように使われるかを見ていきましょう。
/ ——————————————————————— /
/ FILE DEFINITION: /
/ 入力ファイル (SYSUT1) と出力ファイル (SYSUT2) を定義します。 /
/ ここでは、入出力とも BUFFERED 属性を指定して、I/O効率化を図ります。 /
/ ——————————————————————— /
DCL INPUT_FILE FILE RECORD SEQUENTIAL ENV(BUFFERED); / 入力ファイル定義 /
DCL OUTPUT_FILE FILE RECORD SEQUENTIAL ENV(BUFFERED); / 出力ファイル定義 /
/ ——————————————————————— /
/ DATA DECLARATION: /
/ ファイルから読み込むデータと、ファイルに書き込むデータを格納する /
/ ための変数を宣言します。 /
/ ‘VARYING’ は可変長文字列であることを示します。 /
/ ‘BASED’ はポインタ変数によって管理されることを示しますが、 /
/ ここでは単純なレコード処理として扱います。 /
/ ——————————————————————— /
DCL 1 INPUT_RECORD,
2 EMP_ID CHAR(8) INIT(‘ ‘); / 従業員ID (8文字) /
DCL 1 OUTPUT_RECORD LIKE INPUT_RECORD; / 出力レコード (入力と同じ構造) /
/ ——————————————————————— /
/ PROCEDURE START: /
/ プログラムのエントリーポイントです。 /
/ ——————————————————————— /
PROCESS_RECORDS: PROCEDURE OPTIONS(MAIN);
/ ファイルのオープン /
OPEN FILE(INPUT_FILE) INPUT STREAM SEQUENTIAL;
OPEN FILE(OUTPUT_FILE) OUTPUT STREAM SEQUENTIAL;
/ ファイル読み込みループ /
DO WHILE (TRUE); / 無限ループ (EOFで抜けます) /
/ 入力ファイルから1レコード読み込み /
/ BUFFERED属性のため、実際にはメモリ上のバッファから読み込まれます。/
/ ディスクへのアクセスは、バッファがいっぱいになるなど、/
/ システムが効率的と判断したタイミングでまとめて行われます。/
READ FILE(INPUT_FILE) INTO(INPUT_RECORD);
/ ファイルの終端(EOF)チェック /
/ EOFの場合、ONCODEの値が特定の値になります (例: 100)。 /
/ ここでは簡略化のため、READ文の後にEOFを検出する /
/ 別の仕組みがあることを想定しています。 /
/ 実際には、ON ENDFILE(INPUT_FILE) などの例外処理を使います。 /
IF EOF(INPUT_FILE) THEN / 実際にはEOF(FILE)はPL/Iの標準関数ではありません。 /
/ ON ENDFILE 例外処理が一般的です。 /
LEAVE; / ループを抜ける /
END;
/ データの加工処理 (例: 従業員IDをコピー) /
OUTPUT_RECORD = INPUT_RECORD;
/ 出力ファイルへ1レコード書き込み /
/ BUFFERED属性のため、実際にはメモリ上のバッファに書き込まれます。/
/ ディスクへの書き込みは、バッファがいっぱいになるなど、/
/ システムが効率的と判断したタイミングでまとめて行われます。/
WRITE FILE(OUTPUT_FILE) FROM(OUTPUT_RECORD);
END; / DO WHILE /
/ ファイルのクローズ /
CLOSE FILE(INPUT_FILE);
CLOSE FILE(OUTPUT_FILE);
PUT SKIP LIST(‘処理が正常に完了しました。’);
END PROCESS_RECORDS;
コードのポイント解説:
- `DCL INPUT_FILE FILE RECORD SEQUENTIAL ENV(BUFFERED);`
- `DCL` は `DECLARE` の略で、変数を宣言するコマンドです。
- `INPUT_FILE` はファイルの名前です。
- `FILE` は、これがファイルであることを示します。
- `RECORD` は、ファイルをレコード(行)単位で読み書きすることを意味します。
- `SEQUENTIAL` は、ファイルが順番に読み書きされることを示します。
- `ENV(BUFFERED)` が今回の主役!ここで、このファイルへのI/Oはバッファリングされる、と指定しています。
- `READ FILE(INPUT_FILE) INTO(INPUT_RECORD);`
- `INPUT_FILE` から1レコードを読み込み、`INPUT_RECORD` という変数に格納します。
- `BUFFERED` 属性のおかげで、この `READ` 文が発行されるたびにディスクアクセスが発生するわけではなく、メモリ上のバッファが満たされるまで、あるいはシステムが必要と判断するまで、ディスクアクセスは保留されます。
- `WRITE FILE(OUTPUT_FILE) FROM(OUTPUT_RECORD);`
- `OUTPUT_RECORD` の内容を `OUTPUT_FILE` に書き込みます。
- こちらも `BUFFERED` 属性により、ディスクへの実際の書き込みはまとめて行われます。
補足:
- `EOF(INPUT_FILE)` の部分について、PL/Iの標準的なファイルI/Oでは、ファイル終端の検出は `ON ENDFILE` 例外処理を使って行うのが一般的です。上記のコード例は、概念を分かりやすくするために簡易的な表現にしています。
- `DCL 1 INPUT_RECORD, 2 EMP_ID CHAR(8) INIT(‘ ‘);` のような構造化データ宣言や、`LIKE` キーワードなどもPL/Iならではの機能です。最初は難しく感じるかもしれませんが、これもデータ構造を分かりやすく定義するための便利な機能なんですよ。
どんな時にBUFFERED属性を使うべきか?
- 大量のデータを読み書きするバッチ処理: これが `BUFFERED` 属性の最も得意とするところです。I/O回数を減らすことで、処理時間を劇的に短縮できます。
- ディスクI/Oがパフォーマンスのボトルネックになっている場合: プロファイリングなどでI/O処理に時間がかかっていることが分かっている場合に、まず試すべき改善策の一つです。
逆に、UNBUFFERED属性が適しているのは?
- リアルタイム性が求められる処理: データを書き込んだら、すぐにディスクに反映させたい、といったケースでは `UNBUFFERED` が必要になることがあります。
- 非常に小さなファイルを、かつランダムにアクセスする場合: バッファリングによるオーバーヘッドよりも、直接アクセスの方が速い場合も稀にありますが、バッチ処理ではあまり見られません。
まとめ
PL/Iの `BUFFERED` 属性は、バッチ処理のパフォーマンスを向上させるための強力なツールです。ファイル定義に `ENV(BUFFERED)` を追加するだけで、I/O回数が劇的に減り、処理時間が短縮されることが期待できます。
最初は「なんだか難しそう…」と感じるかもしれませんが、一つ一つのキーワードや属性の意味を理解し、実際のコードで試してみることで、PL/Iの持つパワフルな機能があなたのものになっていきます。
メインフレームの世界は、古き良き技術が息づいている、宝の山のような場所です。これからも、このブログで、皆さんと一緒に、PL/Iやメインフレームの魅力を紐解いていけたら嬉しいです!
