オープンソース・ソフトウェアの開発とダウンロード

CVS リポジトリの参照

Contents of /perldocjp/docs/modules/File-Slurp-9999.01/extras/slurp_article.pod

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.2 - (show annotations) (download)
Thu Jan 27 13:14:53 2011 UTC (13 years, 3 months ago) by iwai
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +3 -0 lines
add encoding tag

1
2 =encoding euc-jp
3
4 =head1 Perl Slurp概要
5
6 =head2 はじめに
7
8 Perlでよくみかけるコードはテキストファイルを行単位に処理することです:
9
10 while( <FH> ) {
11 do something with $_
12 }
13
14 このコードは複数の変数を持っています。しかしキーポイントは各ループの
15 繰り返しの度にファイルから1行だけ読み込むことです。これには
16 メモリを1行に使われるだけに制限できる、(STDINを経由してパイプされた
17 データも含めて)どんな大きさのファイルも扱うことができる、Perl初心者にも
18 教えやすく理解しやすいといった、いくつかの利点があります。実際のところ、
19 初心者は以下のような馬鹿げたことをしがちです:
20
21 while( <FH> ) {
22 push @lines, $_ ;
23 }
24
25 foreach ( @lines ) {
26 do something with $_
27 }
28
29 行毎の処理は素晴らしいのですが、ファイルの読み込みを処理する唯一の方法では
30 ありません。他のよくあるスタイルはファイル全体をスカラーや配列に読み込む
31 ことです。これは俗に丸呑み(slurping)といわれています。現在、丸呑みの
32 評判はあまりよくありません。この記事はそれを社会復帰させようとしています。
33 ファイルの丸呑みには利点と制限があります。行毎の処理がよい場合には
34 単純に行うべきものではありません。一度に処理するため、メモリ上に
35 ファイル全体を必要とするときには最もよいことです。
36 適切に行われれば、メモリ処理を伴う丸呑みは、行ごとに処理するよりも
37 速く、コードをシンプルにします。
38
39 丸呑みを見ていて一番の問題はファイルの大きさです。非常に大きなファイル
40 やSTDINからデータの量がわからないものを丸呑みすることは、メモリ使用に
41 損害をもたらし、スワップ・ディスクをスラッシングさせてしまうかもしれません。
42 メモリ使用に悪影響を与えることなく最大サイズの入力を扱うことが分かっている
43 場合にのみ、STDINを丸呑みすることができます。そこで私はディスク・ファイルだけを、
44 それもほどほどの大きさであることを知っていて、ファイルを丸ごと処理する
45 ちゃんとした理由がある場合にのみ丸呑みすることを主張します。
46 今日の、ほどほどの大きさはRAMが限られていた昔よりも大きいことに
47 注意してください。メガバイトを丸呑みしても、ほとんどのシステムでは
48 問題はないでしょう。しかし私が丸呑みしようとする、ほとんどのファイルは
49 それよりも大分小さくなりがちです。丸呑みがうまくいく典型的なファイルは
50 設定ファイル、(ミニ-)言語スクリプト、いくつかのデータ(特にバイナリ)ファイル、
51 そして早く処理する必要がある大きさが分かっている他のファイルです。
52
53 行単位よりに丸呑みが勝利する大きなもう一つの点は速度です。Perlの
54 IOシステムは(他の多くと同様に)遅いものです。行ごとにC<< <> >>を呼び出す
55 ことは、行末をチェックし、EOFをチェックし、行をコピーし、内部ハンドル
56 構造体を加工するなどを行います。行の読み込みのために各行のたくさんの
57 作業が行われます。これに反して丸呑みは、もし正しければ、通常
58 1回だけのI/O呼び出しを伴い、余分なデータのコピーはありません。
59 ディスクへの書きこみでも同じことがいえます。そして私たちは
60 それもカバーします(端末丸呑みが伝統的な読みこみ操作であっても、
61 ファイル全体へのI/Oを1回の操作で行うという考え方のため、
62 "丸呑み"という言葉を使います)。
63
64 最後にファイル全体をメモリ上に丸呑みしたときには、行単位の処理を
65 行うことは可能ではないか、簡単ではないデータを操作することができます。
66 これには(改行は無視した)グローバルな検索/置換、C<//g>を呼び出すことで
67 全てのマッチを捕らえること、複雑な解析(これは多くの場合、改行を無視
68 しなければなりません)、*MLの処理(そこでは行の末尾は単なる空白です)、
69 テンプレート展開のような複雑な変形が含まれます。
70
71 =head2 グローバルな操作
72
73 丸呑みされたファイル全体に対して早く簡単に行うことができる、いくつかの
74 簡単なグローバルな操作を以下に示します。それらは行単位の処理によっても
75 行うこともできます。しかしそれはより遅くより多くのコードを必要と
76 します。
77
78 よくある問題は、キー/値の組を持ったファイルを読み込むことです。これを
79 行うモジュールもあります。しかし簡単なフォーマットのためにそれらが
80 必要な人はいるでしょうか?単純にファイルを丸呑み、全てのキー/値のペアを
81 全て捕らえるために一回の解析を行ってください。
82
83 my $text = read_file( $file ) ;
84 my %config = $test =~ /^(\w+)=(.+)$/mg ;
85
86 行の始まりにあるキー(C</m>修飾子のために文字列のどこにあっても)、
87 =文字、そして行の末尾にあるテキスト(再びC</m>がその働きをおこないます)に
88 マッチします。実際には末尾C<$>も必要ですらありません。というのも
89 C<.>は通常は改行にマッチしないからです。キーと値が捕らえられ、C<m//>は
90 C</g>修飾子付きでリスト・コンテキストであるために、それは全ての
91 キー/値の組を捕らえ、それらを返します。C<%config>ハッシュは、このリストが
92 代入され、完全に解析されたファイルをハッシュに持っていることになります。
93
94 私が働いてきたさまざまなプロジェクトでは、いくつかの簡単なテンプレートを
95 必要としていました。そして私は完全なモジュールを使う気にはなれませんでした。
96 (お願いですから、あなたの好きなテンプレート・モジュールについて熱くならない
97 でください :-)。そこで私はテンプレート・ファイルを読みこみ、
98 テンプレート・ハッシュを設定し、これを1行で行ってきました:
99
100 $text =~ s/<%(.+?)%>/$template{$1}/g ;
101
102 これはファイル全体が丸呑みされている場合にのみ機能します。ほんの少しの
103 余分な作業で拡張されるテキストの固まりを扱うことができます:
104
105 $text =~ s/<%(\w+)_START%>(.+)<%\1_END%>/ template($1, $2)/sge ;
106
107 マーカーの間のテキストを展開するためにC<template>サブルーチンを与えて
108 みてください。そして最小限のコードで簡単なシステムを持つことになります。
109 これは上手くいき、C</s>修飾のために複数行を捕らえることに注意してください。
110 これは行単位の処理では扱いにくいものです。
111
112 これは非常に簡単なテンプレート・システムです。そして直接、ネストしたタグを
113 やそのほかの複雑な機能を扱うことはできないことに注意してください。しかし
114 CPAN上にある数多くのテンプレート・モジュールの一つを使うとしても、
115 ファイルの読みこみと書き込みのための、より早い方法を持つことによって
116 利益を得ます。
117
118 配列にファイルを丸呑みすることにも、いくつかの便利な利点があります。
119 1つの簡単な例は、各レコードがC<:>のような文字により分割されたフィールドを
120 持っているフラットなデータベース・ファイルを読み込むことです:
121
122 my @pw_fields = map [ split /:/ ], read_file( '/etc/passwd' ) ;
123
124 丸呑みされたファイルの行にランダムなアクセスすることは他の利点があります。
125 行の配列を検索することを早くするために行インデックスを構築することも
126 できます。
127
128
129 =head2 伝統的な丸呑み
130
131 Perlは常に最小限のコードでファイルの丸呑みをサポートしてきました。
132 ファイルを行のリストに読み込むことはささいなことです。単純にC<< <> >>
133 演算子をリスト・コンテキストで呼び出すだけです:
134
135 my @lines = <FH> ;
136
137 そしてスカラーに丸呑みすることは、さらに多くの作業はありません。
138 組込変数C<$/>(入力レコード分割子)を単純に未定義値に設定し、
139 C<< <> >>でファイルを読み込むだけです:
140
141 {
142 local( $/, *FH ) ;
143 open( FH, $file ) or die "sudden flaming death\n"
144 $text = <FH>
145 }
146
147 C<local()>を使っていることに注意してください。これはC<$/>をC<undef>に設定し、
148 スコープを抜けるときには、C<$/>は前の値に戻します(ほとんどの場合にはおそらく
149 "\n"です)。
150
151 以下のPerlのコードはC<$text>変数が宣言されることを可能にしています、
152 しっかりとネストされたブロックには何も必要ありません。C<do>ブロックは
153 スカラーコンテキストでC<< <FH> >> を実行し、ファイルをC<$text>に
154 読みこみます:
155
156 local( *FH ) ;
157 open( FH, $file ) or die "sudden flaming death\n"
158 my $text = do { local( $/ ) ; <FH> } ;
159
160 それらの読み込は両方とも、5.005との互換性のためにロカール化された
161 ファイル・ハンドルを使っています。以下のものは5.6.0の
162 自動的に生成されるレキシカルなハンドルを使っています:
163
164 {
165 local( $/ ) ;
166 open( my $fh, $file ) or die "sudden flaming death\n"
167 $text = <$fh>
168 }
169
170 open( my $fh, $file ) or die "sudden flaming death\n"
171 my $text = do { local( $/ ) ; <$fh> } ;
172
173 そしてこれは、open呼び出しの必要を削除した、そのコードの変形です:
174
175 my $text = do { local( @ARGV, $/ ) = $file ; <> } ;
176
177 C<$file>の中のファイル名はローカル化されたC<@ARGV>に設定され、
178 nullのファイル・ハンドルはC<@ARGV>の中のファイルからデータを読み込むために
179 使われます。
180
181 スカラーに代入する代わりに、上記の丸呑み全てを配列に代入することができます。
182 そしてそれはファイルを取得しますが、(C<$/>が行の終わりの印として
183 使われて)行に分割されます。
184
185 それらの丸呑みのよくある変形があります。これは非常に遅く、よいコードでは
186 ありません。ざっとみてください、これはほとんど常にお荷物のコードです:
187
188 my $text = join( '', <FH> ) ;
189
190 入力ファイルを不必要に行に分割し(C<join>がC<< <FH> >>をリスト・コンテキスト
191 にします)、そしてそれらの行を再びつなげています。この慣用句の元のコーダは
192 明らかにI<perlvar>を読んだことがなく、スカラーの丸呑みを可能にするための
193 C<$/>の使い方を学んだことがありません。
194
195 =head2 書き込みのための丸呑み
196
197 ファイル全体を一度に読み込むのは一般的ですが、ファイル全体を書き込む
198 こともできます。ファイルの読み込みのとき、私たちはそれを"丸呑み(slurping)"
199 と呼びました。しかし書き込み操作について一般的に受け入れられている言葉は
200 ありません。私は何人かのPerl仲間に尋ねてみました。すると2つの興味深い
201 推薦をえることができました。Peter Scottはそれを"burping"(=丸出しする)
202 ("slurping"と韻をふみ、逆方向での動きを示唆します)。推薦されたもう1つは
203 "spewing"(=吐き出す)です。これはより強い視覚的なイメージを持っています :-)
204 あなたの好みを教えるか、あなた独自のものを提案してください。このセクションでは
205 私は両方を使います。そのためそれらがどのように機能するかが分かるでしょう。
206
207 ファイルの吐き出しは丸呑みより、さらに簡単な操作です。心配するような
208 コンテキストの問題はありません。そしてバッファを返すことについて効率の
209 問題は何もありません。以下に簡単な吐き出しサブルーチンです:
210
211 sub burp {
212 my( $file_name ) = shift ;
213 open( my $fh, ">$file_name" ) ||
214 die "can't create $file_name $!" ;
215 print $fh @_ ;
216 }
217
218 入力テキストをコピーせず、@_を直接printに渡していることに注意して
219 ください。後ほどより速い変形を見ることになります:
220
221 =head2 CPAN上での丸呑み
222
223 あなたが予想するようにCPANにはあなたに代わってファイルを丸呑みするモジュール
224 があります。私はSlurp.pm(Rob Casey - CPANではROBAU)と
225 File::Slurp.pm (David Muir Sharnoff - CPANではMUIR)という2つを見つけました。
226
227 Slurp.pmからのコードを以下に示します:
228
229 sub slurp {
230 local( $/, @ARGV ) = ( wantarray ? $/ : undef, @_ );
231 return <ARGV>;
232 }
233
234 sub to_array {
235 my @array = slurp( @_ );
236 return wantarray ? @array : \@array;
237 }
238
239 sub to_scalar {
240 my $scalar = slurp( @_ );
241 return $scalar;
242 }
243
244 +C<slurp()>サブルーチンは、スカラーや配列に丸呑みすることをサポートするために
245 C<$/>を未定義値にするという方法と特別なファイルハンドルC<ARGV>を使っています。
246 また呼び出し元が読み込みのコンテキストを制御することを可能にする2つの
247 ラッパー・サブルーチンを提供しています。そしてC<to_array()>はC<wantarray>を
248 チェックすることにより、その呼び出し元のコンテキストに従って丸呑みされた
249 行のリストや無名リストを返します。C<@EXPORT>には'slurp'が入っており、
250 C<@EXPORT_OK>には3つのサブルーチンが入っています。
251
252 <脚注: Slurp.pm は名前の付け方としてはよくありません。それはトップレベルの
253 名前空間にあるべきではありません。>
254
255 元のFile::Slurp.pmにはこのコードが入っています:
256
257 sub read_file
258 {
259 my ($file) = @_;
260
261 local($/) = wantarray ? $/ : undef;
262 local(*F);
263 my $r;
264 my (@r);
265
266 open(F, "<$file") || croak "open $file: $!";
267 @r = <F>;
268 close(F) || croak "close $file: $!";
269
270 return $r[0] unless wantarray;
271 return @r;
272 }
273
274 このモジュールはC<read_file()>も含めていくつかのサブルーチンを提供しています
275 (他のものについては後述します)。C<read_file()>は呼び出し元のコンテキストに
276 よって、行のリストや1つのスカラーを丸呑みするという点でC<Slurp::slurp()>と
277 同じように振舞います。これもスカラーの丸呑みのためにC<$/>を未定義値に
278 するという方法を取っています。しかしローカル化されたC<@ARGV>を使い、
279 他のモジュールが行うのではなく、明示的なopen呼び出しを使っています。
280 行の無名配列を取得する方法も提供していません。
281 しかしそれは、無名配列コンストラクタC<[]>の内側で、それを呼ぶことに
282 より簡単に修正することができます。
283
284 これらのモジュールの両方はPerlコーダにファイルを丸呑みすることを簡単にします。
285 それは両方ともスカラー・モードで読み込むためにC<$/>の方法を、
286 行で丸呑みするためのリスト・コンテキストではC<< <> >>の自然な動きを
287 用いています。しかしどちらもスピードの最適化されていませんし、
288 バイナリあるいはUnicodeファイルをサポートするためにC<binmode()>を
289 扱うこともできません。読み込みの機能やスピードアップについての詳細は
290 下記をご覧ください。
291
292 =head2 丸呑みAPIの設計
293
294 CPANにある丸呑みモジュールは非常に簡単なAPIを持っています。そして
295 C<binmode()>をサポートしていません。このセクションは、リファレンスによる
296 効率的な戻り値、C<binmode()>、そして呼び出しのバリエーションなど、
297 さまざまなAPI設計問題をカバーします。
298
299 呼び出しのバリエーションから始めましょう。読み込まれたファイルは4つの
300 フォーマットで返される可能性があります:単一のスカラー、スカラーへのリファレンス、
301 行のリスト、行の無名配列。しかし呼び出しものは2つのコテキストしか
302 提供できません:スカラーかリストかです。そこで私たちは、(Slurp.pmが
303 やっているように)1つ以上のサブルーチンでAPIを提供するか、
304 File::Slurpがやっているようにスカラーか(無名配列ではなく)リストを
305 返す1つのしかサブルーチンを提供しないかどちらかです。
306
307 私は独自のC<read_file()>サブルーチンを長年使ってきました。しかし
308 それはFile::Slurpと同じAPIを持っています:コンテキストによって
309 スカラーか行の配列を返す1つのサブルーチンでした。しかしファイル
310 丸呑みのために無名配列を欲しがっている人の興味を理解しています。
311 あるサブルーチンから他のサブルーチンに渡すのがより簡単です、
312 それはC<return>による行の余分なコピーを除去します。そこで私のモジュールは
313 たった1つの丸呑みサブルーチンを提供します。それは
314 コンテキストと渡されたフォーマットのオプションを基にファイルデータを
315 返します。スカラーやリストで丸呑みする特別なサブルーチンは必要ありません。
316 汎用的なC<read_file()>サブルーチンが適切なコンテキストでデフォルトで
317 それを行います。もしC<read_file()>にスカラー・リファレンスや
318 行の無名配列を返して欲しければ、それらのフォーマットをオプションで
319 要求することができます。スカラーにリファレンスを渡し(例えば
320 前もって確保されているバッファ)、丸呑みされたデータを入れさせることもできます
321 (そしてこれは最も速い丸呑みモードの1つです。詳細はベンチマークの
322 セクションをご覧ください)。もしスカラーを配列に丸呑みさせたければ、
323 単純に望まれる配列要素を選択するだけです。すると
324 C<read_file()>サブルーチンにスカラー・コンテキストを提供します。
325
326 カバーする次の領域は、丸呑みサブルーチンの名前です。私はC<read_file()>で
327 いきます。これは叙述的ですし、現在の簡単なものと互換性があります。
328 そして'slurp'とうあだ名を使いません(そのあだ名はモジュール名ですけれども)。
329 また私はFile::Slurp名前空間を保持することにしました。これは親切なことに、
330 その現在の所有者であるDavid Muirにより私へ渡されました。
331
332 APIを設計するときもう1つの重大な領域は、引数の渡し方です。
333 C<read_file()>サブルーチンは1つの必須な引数を取ります。それはファイル名です。
334 C<binmode()>をサポートするため、私たちはもう1つのオプションの
335 引数を必要とします。リファレンスより丸呑みされたスカラーを返すことを
336 サポートするため、3番目のオプションの引数が必要です。私は最初、考えたことは
337 位置による3つの引数-ファイル名、バッファ・リファレンス、そしてbinmodeを
338 もつAPIを設計することでした。しかしbinmodeを設定したいけれど、バッファ・
339 リファレンスを渡したくなければ、2番目の引数をC<undef>で埋めなければなりません。
340 これでは格好よくありません。そこで私はファイル名を位置によるものとし、他の
341 2つを名前付きとしました。そのサブルーチチンは以下のようにはじまります:
342
343 sub read_file {
344
345 my( $file_name, %args ) = @_ ;
346
347 my $buf ;
348 my $buf_ref = $args{'buf'} || \$buf ;
349
350 もう一つのサブルーチン(C<read_file_lines()>)は、オプションのbinmodeしか
351 取りません(そのためバイナリの分割子を持ったファイルを読み込むことができます)。
352 それはスカラー・コンテキストでは無名配列を返すことができるので、
353 バッファ・リファレンスの引数を必要としません。そのためこのモジュールは
354 位置の引数を使うことができますが、C<read_file()>のAPIにそのAPIを似せるために、
355 オプションの引数のための名前による引渡しも使います。これは古いコードを
356 壊すことなく新しいオプションの引数を後から追加することができるということも
357 意味しています。両方のサブルーチンのためにAPIを同じことによる思わぬ贈り物は、
358 2つのサブルーチンがどのように、一緒に機能するように最適化されたかを
359 見ることができることです。
360
361 出力での丸呑み(あるいは吐き出し、丸出し :-))も、同様にそのAPIを
362 設計する必要があります。最も大きな問題はオプションの引数をサポートする
363 必要があるだけでなく、書き出される引数のリストも必要になることです。
364 Perl6はオプションの名前付き引数と最後のslurp引数で扱うことができるでしょう。
365 これはPerl 5なので、いくらか知恵を使う必要があります。
366 最初の引数はファイル名、そしてそれはC<read_file>サブルーチンと同様に
367 位置よる引数になります。しかしオプションの引数を、そしてデータのリストを
368 どのように渡すことができるでしょうか?解決は、データ・リストには決して
369 リファレンスが入らないという事実にあります。吐き出し/丸出し(=Burping/Spewing)は、
370 プレーンなデータにのみ機能します。そのためもし次の引数が
371 ハッシュ・リファレンスであれば、それにはオプションの引数が入っていて、
372 残りの引数がデータリストだと考えることができます。そこでC<write_file()>は
373 以下のように始まります:
374
375 sub write_file {
376
377 my $file_name = shift ;
378
379 my $args = ( ref $_[0] eq 'HASH' ) ? shift : {} ;
380
381 オプションの引数が渡れても、渡されなくても、余分なコピーを最小限にするため、
382 データ・リストをC<@_>に残しておきます。C<write_file()>をこのように呼び出す
383 ことができます:
384
385 write_file( 'foo', { binmode => ':raw' }, @data ) ;
386 write_file( 'junk', { append => 1 }, @more_junk ) ;
387 write_file( 'bar', @spew ) ;
388
389 =head2 高速な丸呑み
390
391 私はある時点で$/をundefに設定するよりも速くファイルを丸呑みする方法を
392 学びました。そのほうほうはとても単純です。ファイルの大きさ(これは-s演算子が
393 提供します)でreadを一回呼び出すだけです。これはEOFをチェックをperl内部の
394 I/Oループを迂回し、処理の全てを行います。そこで私は実験を決意し、
395 sysreadがさらに予想よりも速いことがわかりました。sysreadは
396 すべてのPerlのstdioを回避し、カーネル・バッファからファイルを直接
397 Perlスカラーに読みこみます。これがFile::Slurpがsysopen/sysread/syswriteを
398 使っている理由です。コードの残りはすべてさまざまなオプションと
399 データ処理のテクニックをサポートしているだけです。
400
401
402 =head2 ベンチマーク
403
404 ベンチマークは明白にさせ、有益で、いらいらさせたり、勘違いさせるかも
405 しれません。スピードも著しく向上しなければ、新しく、より複雑な
406 丸呑みモジュールを作り出すことを意味がないかもしれません。
407 そこで私はベンチマーク・スクリプトを作成しました。
408 ファイルの大きさと呼び出しコンテキストを変えて、さまざまな丸呑み
409 メソッドを比較するベンチマーク・スクリプトを作成しました。
410 このスクリプトはtarファイルのメインのディレクトリから以下のように
411 実行させることができます:
412
413 perl -Ilib extras/slurp_bench.pl
414
415 コマンドラインで一つの引数を渡すと、それはtimethese()に渡され、
416 それがその期間を制御します。そのデフォルトは-2で、それはcpu時間で
417 少なくとも2秒まで各ベンチマークを実行させます。
418
419 以下の数値は私の300Mhz sparcで行った実行からのものです。
420 あなたのマシンでは、もっと速いカウントを得ることでしょう。しかし
421 相対的なスピードはあまり変わらないはずです。どうかあなたの結果と
422 PerlそしてOSのバージョンを送ってください。またベンチマーク・スクリプトで
423 遊んだり、より多くの丸呑みのバリエーションやデータファイルを追加する
424 こともできます。
425
426 このセクションの残りでは、ベンチマークの結果について説明します。
427 個々のベンチマークのためのコードを見るため、extras/slurp_bench.plを
428 参照することができます。メンチマーク名がcpan_で始まっていれば、
429 Slurp.pmかFile::Slurp.pmのどちらかからのものです。new_から始まるものは
430 新しいFile::Slurp.pmからのものです。file_contents_で始まるものは
431 クライアントのコードが基です。私が作った残りは、ベンチマークの
432 ある視点を明確にするため私が作成したバリエーションです。
433
434 小さいファイル、大きいファイルのデータは以下のようにつくられます:
435
436 my @lines = ( 'abc' x 30 . "\n") x 100 ;
437 my $text = join( '', @lines ) ;
438
439 @lines = ( 'abc' x 40 . "\n") x 1000 ;
440 $text = join( '', @lines ) ;
441
442 そのため小さいファイルは9,100バイト、大きいファイルは121,000バイト
443 です。
444
445 =head3 小さいファイルのスカラー丸呑み
446
447 file_contents 651/s
448 file_contents_no_OO 828/s
449 cpan_read_file 1866/s
450 cpan_slurp 1934/s
451 read_file 2079/s
452 new 2270/s
453 new_buf_ref 2403/s
454 new_scalar_ref 2415/s
455 sysread_file 2572/s
456
457 =head3 大きいファイルのスカラー丸呑み
458
459 file_contents_no_OO 82.9/s
460 file_contents 85.4/s
461 cpan_read_file 250/s
462 cpan_slurp 257/s
463 read_file 323/s
464 new 468/s
465 sysread_file 489/s
466 new_scalar_ref 766/s
467 new_buf_ref 767/s
468
469 上記の数値を見たときに得られる主要な結論は、ファイルをスカラーに
470 丸呑みするとき、スカラー・リファレンスによって結果を返すことによって、
471 ファイルが大きいほど多くの時間を節約することができるということです。
472 余分なバッファ・コピーは加算してしまいます。それほどない、より柔軟な
473 新しいモジュールのオーバーヘッドを明確にさせるために追加された非常に
474 単純なsysread_fileのエントリを除けば新しいモジュールは全ての中で
475 トップになっています。リストを丸呑み、それからjoinするため、
476 file_contentsは常に最下位です。それは極めて遅い、古臭い初心者と
477 お荷物崇拝(? cargo culted)スタイルです。またfile_contentsでの
478 OOコードはさらに遅くさせています(私はこれを見せるために
479 file_contents_no_OOエントリを入れています)
480 2つのCPANモジュールは小さいファイルについてはかなりのものです。
481 しかしファイルがより大くなると、新しいモジュールにくらべてのろまです。
482
483 =head3 小さいファイルのリスト丸呑み
484
485 cpan_read_file 589/s
486 cpan_slurp_to_array 620/s
487 read_file 824/s
488 new_array_ref 824/s
489 sysread_file 828/s
490 new 829/s
491 new_in_anon_array 833/s
492 cpan_slurp_to_array_ref 836/s
493
494 =head3 大きいファイルのリスト丸呑み
495
496 cpan_read_file 62.4/s
497 cpan_slurp_to_array 62.7/s
498 read_file 92.9/s
499 sysread_file 94.8/s
500 new_array_ref 95.5/s
501 new 96.2/s
502 cpan_slurp_to_array_ref 96.3/s
503 new_in_anon_array 97.2/s
504
505 これがおそらくこのベンチマークで最も面白い結果でしょう。5つの
506 異なるエントリが効率的にリードに結び付けられています。
507 論理的な結論はとしては、ファイルがどのように丸呑みされたかではなく、
508 入力を行に分割することが境界となる操作だということです。
509 これは新しいモジュールが明確な勝者でならない唯一のベンチマークです。
510 (大きなファイルのエントリでは勝者でした - 小さいファイルでは
511 僅差で2番目でした)。
512
513
514 注意: 全ての吐き出しエントリについてのベンチマークの情報では、
515 各行の終わりにある余分な数は、エントリ全体にかかる実時間での秒数です。
516 ベンチマークは少なくとも各エントリを2CPU秒実行します。以上に
517 大きい実時間は下記で説明します。
518
519 =head3 小さいファイルのスカラー吐き出し
520
521 cpan_write_file 1035/s 38
522 print_file 1055/s 41
523 syswrite_file 1135/s 44
524 new 1519/s 2
525 print_join_file 1766/s 2
526 new_ref 1900/s 2
527 syswrite_file2 2138/s 2
528
529 =head3 大きいファイルのスカラー吐き出し
530
531 cpan_write_file 164/s 20
532 print_file 211/s 26
533 syswrite_file 236/s 25
534 print_join_file 277/s 2
535 new 295/s 2
536 syswrite_file2 428/s 2
537 new_ref 608/s 2
538
539 スカラーの吐き出しエントリでは、スカラー・バッファへの
540 リファレンスを渡されたとき、新しいモジュールAPIが勝っています。
541 C<syswrite_file2>は小さいファイルでは、そのよりシンプルなコードにより、
542 それを打ち破っています。古いCPANモジュールは余分なデータのコピーと
543 printを使っていることにより、最も遅くなっています。
544
545 =head3 小さいファイルのリスト吐き出し
546
547 cpan_write_file 794/s 29
548 syswrite_file 1000/s 38
549 print_file 1013/s 42
550 new 1399/s 2
551 print_join_file 1557/s 2
552
553 =head3 大きいファイルのリスト吐き出し
554
555 cpan_write_file 112/s 12
556 print_file 179/s 21
557 syswrite_file 181/s 19
558 print_join_file 205/s 2
559 new 228/s 2
560
561 ここでも、単純なC<print_join_file>エントリが小さなリストのファイルへの
562 吐き出しのとき新しいモジュールを破っています。しかしファイルが大きくなると
563 新しいモジュールに負けています。最初に行の余分なコピーをおこない、それから
564 出力リストに対するC<print>を呼び出すために、それはC<print>にjoinによって
565 生成された単一のスカラーを渡すよりもだいぶ遅いのです、古いCPANモジュールは
566 他のものに遅れをとっています。C<print_file>エントリは直接C<@_>を出力する
567 ことの利点をあらわしています。そしてC<print_join_file>はjoinの最適化を
568 加えています。
569
570 それでは長い実時間について考えていましょう。
571 吐き出しの全てのエントリのベンチマーク・コードを注意深く見れば
572 いくつかは常に新しいファイルを出力し、あるものは既存のファイルを
573 上書きしていることがわかるでしょう。古いFile::SlurpがなぜC<overwrite>
574 サブルーチンを持っているのかDavid Muirに聞いたところ、
575 彼はファイルの上書きによって、ファイルの中の幾分かは読み込むことができる
576 ことが常に保証されると答えました。もし新しいファイルを作成すると、
577 ファイルが作成されたけれどもデータがないという瞬間があります。
578 しかし私はそれが十分な答えだとは感じませんでした。上書きの場合でも、
579 既存のファイルよりも小さいファイルを書き、新しい大きさにファイルを切り落とす
580 ことができます。Windowsの種類(small race window)によっては、他のプロセスが
581 ファイルの前のバージョンから残されたガラクタがついた新しいデータを
582 丸呑みすることがあるものもあります。これは一貫性のあるファイル・データを
583 確実にする唯一の方法はファイル・ロックを適切にするという点を
584 強くします。
585
586 しかしこれらの長い実時間についてはどうでしょう?そうです、それは全てファイルを
587 作成と既存のものを上書きの違いについてです。前のものは新しいiノード
588 (あるいは他のファイルシステムでの同様のもの)を確保する必要があります。
589 そして後者は既存のiノードを再利用することができます。これは上書きすることは
590 ディスクのシークと同時にCPU時間を節約することを意味します。実際、
591 ベンチマークを実行すると、吐き出し処理の間、iノードを確保するために
592 ディスクが狂ったようになることを聞くことができます。このCPUと実時間の
593 両方のスピードアップは、新しいモジュールがファイルを吐き出すとき、
594 常に上書きを行うためです。これは適切な切捨ても行い(そしてこれは
595 既に以前書かれた大きいファイルの後で小さいものを吐き出すことにより、
596 テストの中でチェックされます)。C<overwrite>サブルーチンはC<write_file>
597 への単なるtypeglobエイリアスであり、古いFile::Slurpモジュールとの
598 後方互換性のためにあります。
599
600 =head3 ベンチマークの結論
601
602 簡単なエントリがそれを破ってしまう2、3のケースを除けば、新しいFile::Slurpは
603 スピードのリーダーあるいはリーダーの中の1つです。そのリファレンスによりバッファを
604 渡す特別なAPIはスピードアップにとても有効であることを証明しました。
605 また、C<sysread/syswrite>の利用や出力行のjoinなど、その他の最適化を全て使
606 っています。私は広く丸呑みを使っている多くのプロジェクトが特に新しいAPIの
607 機能を得るようコードを書き換えたならば、スピードの改善に気が付くことを
608 期待しています。コードを触らなくても、そして簡単なAPIを使っていても、
609 著しいスピードアップを得るでしょう。
610
611 =head2 エラーの取り扱い
612
613 SlurpサブルーチンはファイルがオープンできるかやI/Oエラーのような状態を
614 気にしています。これらのエラーをどのように扱い、呼び出し元が何を見るのかは、
615 APIの設計での重要な視点です。丸呑みのための旧式のエラー扱いでは、
616 C<die()>、もう少しよければC<croak()>を呼び出してきました。
617 しかし時には丸呑みにC<warn()>/C<carp()>やエラーを扱うためのコードを
618 使って欲しいでしょう。ええ、これは致命的なエラーを捕まえるため
619 丸呑みをC<eval>ブロックで囲むことにより行うことができます。
620 しかし全ての人がその特別なコードが欲しいわけではありません。そこで私は
621 エラー扱いを選択する別のオプションを全てのサブルーチンに追加しました。
622 もし'err_mode'オプションが'croak'であれば(これがデフォルトでもあります)、
623 呼ばれたサブルーチンはcroakします。'carp'を設定すればcarpが呼ばれます。
624 そのほかの文字列を設定すると(明示的にしたければ'quiet'を使ってください)、
625 エラー・ハンドラは何も呼ばれません。そして呼び出しものは呼び出しからの
626 エラー・ステータスを使うことができます。
627
628 C<write_file()>データのためには戻り値を使いません。そのため
629 エラーを示すためにfalseステータスを返すことができます。
630 C<read_file()>はその戻り値をデータのために使います。
631 しかしそれでもエラー・ステータスを戻させることができます。
632 スカラーモードでの正常終了した読み込みは定義されたデータ文字列か
633 スカラーまたは配列へのリファレンスになります。そこでそのままの
634 returnがここで機能するでしょう。しかしリスト・コンテキストで行で
635 丸呑みするのであれば、そのままのC<return>は空リストを返してしまいます。
636 これは既存であっても空のファイルから取得したときと同じ値です。そこで、
637 C<read_file()>は私が強く主張しているようなことをします。
638 つまり明示的なC<undef>を返します。スカラー・コンテキストでは、
639 これはまだエラーを返します。そしてリスト・コンテキストでは、
640 戻された最初の値がC<undef>となり、それは先頭の要素としては正しい
641 値ではありません。そのためリスト・コンテキストも検知できるエラー・
642 ステータスを得ることができます:
643
644 my @lines = read_file( $file_name, err_mode => 'quiet' ) ;
645 your_handle_error( "$file_name can't be read\n" ) unless
646 @lines && defined $lines[0] ;
647
648
649 =head2 File::FastSlurp
650
651 sub read_file {
652
653 my( $file_name, %args ) = @_ ;
654
655 my $buf ;
656 my $buf_ref = $args{'buf_ref'} || \$buf ;
657
658 my $mode = O_RDONLY ;
659 $mode |= O_BINARY if $args{'binmode'} ;
660
661 local( *FH ) ;
662 sysopen( FH, $file_name, $mode ) or
663 carp "Can't open $file_name: $!" ;
664
665 my $size_left = -s FH ;
666
667 while( $size_left > 0 ) {
668
669 my $read_cnt = sysread( FH, ${$buf_ref},
670 $size_left, length ${$buf_ref} ) ;
671
672 unless( $read_cnt ) {
673
674 carp "read error in file $file_name: $!" ;
675 last ;
676 }
677
678 $size_left -= $read_cnt ;
679 }
680
681 # voidコンテキストの取り扱い(バッファ・リファレンスによりスカラーを返す)
682
683 return unless defined wantarray ;
684
685 # リスト・コンテキストの取り扱い
686
687 return split m|?<$/|g, ${$buf_ref} if wantarray ;
688
689 # スカラー・コンテキストの取り扱い
690
691 return ${$buf_ref} ;
692 }
693
694 sub write_file {
695
696 my $file_name = shift ;
697
698 my $args = ( ref $_[0] eq 'HASH' ) ? shift : {} ;
699 my $buf = join '', @_ ;
700
701
702 my $mode = O_WRONLY ;
703 $mode |= O_BINARY if $args->{'binmode'} ;
704 $mode |= O_APPEND if $args->{'append'} ;
705
706 local( *FH ) ;
707 sysopen( FH, $file_name, $mode ) or
708 carp "Can't open $file_name: $!" ;
709
710 my $size_left = length( $buf ) ;
711 my $offset = 0 ;
712
713 while( $size_left > 0 ) {
714
715 my $write_cnt = syswrite( FH, $buf,
716 $size_left, $offset ) ;
717
718 unless( $write_cnt ) {
719
720 carp "write error in file $file_name: $!" ;
721 last ;
722 }
723
724 $size_left -= $write_cnt ;
725 $offset += $write_cnt ;
726 }
727
728 return ;
729 }
730
731 =head2 Perl 6での丸呑み
732
733 Perl 6では通常、この記事でかかれたことの多くはお払い箱になるでしょう。
734 Perl 6はファイル・ハンドルにに'slurp'オプションを設定することを可能とし、
735 そのようなハンドルから読み込むとき、ファイルは丸呑みされます。リストと
736 スカラーのコンテキストは、サポートされます。そのため行やスカラーに
737 丸呑みすることができます。特別なコードを呼び出すきっかけに'slurp'プロパティを
738 使うことが出来るので、私はPerl 6での丸呑みのサポートが最適化され、
739 stdioサブシステムを回避することを期待しています。
740 そうでなければ、単に何人かの冒険心に富んだ人たちがPerl 6のための
741 File::FastSlurpをつくるでしょう。Perl 5 モジュールでのコードは簡単に
742 Perl 6の文法と意味に修正できます。ボランティアはいませんか?
743
744 =head2 まとめて
745
746 古臭い行単位の処理とファイル全体をメモリ上で扱うことを比較してきました。
747 ファイルの丸呑みは、適切におこなわれれば、あなたのプログラムを
748 スピードアップさせ、コードを簡単にします。途方もないファイル(ログ、
749 DNAシーケンスなど)やどれくらいのデータを読み込むのか分からないSTDINを
750 丸呑みしないことに気が付くべきです。しかしメガバイトの大きさのファイルを
751 丸呑みすることは典型的な量のRAMがインストールされている今日のシステムでは
752 大きな問題にはならないでしょう。Perlが最初に深く使われるようになったとき
753 (Per 4)、丸呑みは10年前のRAMサイズより小さいものに制限されていました。
754 設定、ソースコード、データなど中に何が入っているかに関わらず、
755 ほどほどの大きさのファイルであればほとんど丸呑みすることができます。
756
757 =head2 謝辞
758
759
760
761 =head1 翻訳者
762
763 川合孝典 (GCD00051@nifty.ne.jp)
764
765 =head2 翻訳の言い訳
766
767 この中ではslupingを「丸呑み」とし、反対の出力のものをspewingを「吐き出し」、
768 burpingを「丸出し」としてみました。他にいい訳語を思いついたら教えてください。
769

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26