• ベストアンサー

awk getlineをもう一度ファイル先頭から

for( 略 ){ for( ファイルを先頭から最後まで読み込め ){ getline < filename 何らかの作業 } } のように、ファイルを最初から最後まで読み込ませるという動作を何回(何百回)も繰り返したいのですが、このままではNFが最大になってしまうのか、一回目しか動作してくれません。 何か手があるのでしょうか? 現在は、最初に全部読み込ませながら二次元配列に格納しています。 が、これからやろうとしているのはファイルサイズが大きすぎ(.csvで、5万行×240列、90MBです)、たぶんメモリに入りきらないので、実行速度が激悪になる可能性があります。 (例えば5時間の作業が50時間になるとか。HDDも重労働とか。)(何であんなにメモリ内で膨れちゃうんでしょうか) 最悪、巨大なバッチファイルをawkに吐き出させ、それを実行させようかと...。 それと、getline < filename するときに、何行目から読み始めてくれとか、何行目を読んでくれとか、1行前を読んでくれということは可能なのでしょうか? 現在は、開始させたい行までから送りしたり、次の行に移る前に現在の行を格納し、次の行に移ったときは格納した値を参照させています。 よろしくお願いいたします。

質問者が選んだベストアンサー

  • ベストアンサー
  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.1

> for( ファイルを先頭から最後まで読み込め ){ > getline < filename この2行の意味がよくわかりません。 あるファイルを読み込んで、終端に達しているかどうかは getlineの戻り値を 見ればいいはずなので、その外側に for があるというのはなぜでしょう? getlineで同じファイルから何度も読むということならさらにその外側にfor がありますし。 > それと、getline < filename するときに、何行目から読み始めてくれとか、 > 何行目を読んでくれとか、1行前を読んでくれということは可能なのでしょうか? どれもできません。 元が90Mバイトのテキストデータをオンコアに収められるかどうかはよくわかりませんが、 たとえば検索をするとかでファイルを何度も読み返すのなら、メモリスワップを 嫌ってやってもかえって逆効果になるような気がします。 もうちょっとデータの性質と本当にやりたいことが明確にならないとはっきりしたことは いえませんけど、別の言語(PerlとかRubyなら配列の内容をデータベースに 放り込むことができます)を使うとか awkでやるにしても前処理を工夫するとかしないといけないのではないでしょうか。

tekcycle
質問者

お礼

> for( ファイルを先頭から最後まで読み込め ){ > getline < filename この2行の意味がよくわかりません。 勿論、while文でも構いません。 forにしたのは、何行目から読み始めろ、という指定がもし可能なら、forで書く方が良いだろうからです。 for文は更に入れ子になっております。 最低二つの入れ子。三つの場合もあります。四つもあるかも知れません。 whileで書くなら for(){ for(){ while (getline < filename >0){ 処理 } } } です。 やりたいことは、 実質3列5万行のような数値データの分析です。 このデータから平均値を算出したり線形回帰分析?を算出したりした物に対して元のデータが上か下か、みたいなことをやります。 5サンプルずつの平均値はどうか、10サンプルずつの平均値はどうか、のようにパラメーターを変えてやる毎に先頭行から読み込み直し、となります。(そのパラメーターのためのforが外側に要るのです) 240列になったのは、そのパラメーターを変えての値を既に計算させた結果です。 勿論、パラメーターを変える度に数値計算させることも可能ですが、計算自体に時間がかかり、外側のforが繰り上がる度に内側のforで同じ計算を何度もやらせることになると、時間的に無視できない物があります。 単純平均程度なら繰り返してもどうにかなるのですが、回帰分析の式だと重くなってしまうのです。 おそらく外側のfor1回につき三時間延長という感じでしょう。 1回だけ三時間延長なら問題なくやるのですが。 先頭から終わりまで読み込ませる回数は、forの入れ子3回の場合、軽く50万回を超えるでしょう。 現在直面している大ファイルのケースですと2万回強のようです。 繰り返しが多いのは今回のテーマと言って良いところで変更できません。 (CPUが8086だったら結果が出るのは数年後だったかも知れません。たぶんHDDが先に死んでいるでしょう。(笑) ) PerlもRubyも、ファイルを1行ずつ読み込ませて処理をしていく、という点では同じなのでしょうか? Perlは先日コマンド一覧程度は見たのですが。 何行目から、とか、現在処理行の一つ前の行と一つ後の行を読め、なんて命令はどちらかにあるでしょうか?

その他の回答 (5)

  • notnot
  • ベストアンサー率47% (4900/10358)
回答No.6

#2です。 他の人へのお礼を読んで、何で二次元配列などがでてくるのかようやくわかりました。awkで行を240分割してから個別に連想配列に格納するなど通常ありえない処理です。見かけ上二次元配列に見えるが、実際はそれぞれの添え字を文字列化して、SUBSEPという文字で連結して1つの文字列にしたものをキーとした連想配列というのはご存知でしょうか? #4の方のご指摘どおり、「設計がそもそも悪いのでは?」ということなので、現状のコードでなく、要求仕様を書かないと、誰もこれ以上のアドバイスはできません。 「こういうファイルがあり、こういう入力が来たらこういう出力がほしい。 これこれの制約の元でどういうプログラムを書けばいいか」 という質問を別途するのが良いでしょう。

tekcycle
質問者

お礼

元のデータの平均値のような数値が240種類(240列分)あるのです。 例えば、5点平均6点平均....というように。 エクセル等の表計算ソフトを想像してください。 左端が時間で、その次と次に、その時刻の実測データが入っています。 これが5万行あります。 このデータを元に、まず平均値等をその横にそれぞれ書き加えているのです。 5 8 12 6 7 というデータなら(実際は5万行)、3点(プロット)平均で 空欄 8.333 8.666 8.333 空欄(未定) とその右に書き加えて行きます。 更にその右には4点平均、5点平均、のデータが算出されます。 単純平均ならともかく、この件のデータはもっと複雑な平均値が横方向に入っていった物です。 その計算だけを先にさせてみたのが今回の巨大ファイルです。 この件だと、元のデータに対して、例えば3点平均と6点平均を比べてどうだとか、そういう処理をしていきます。 ですから、$0のまま格納してしまうと取り出し難いかも知れません。 勿論、最初に$0で一次元配列に格納し、それを使用する際に、全部毎回splitする(2万回×5万行)というやり方も可能でしたが。 今現在裏でこの件のプログラムを動かしていますが(8割方終わりました)、 やっているのは、ファイル先頭からの読み直しを前提として、元のデータとi点平均値、j点平均値の横5~6点を二次元配列に入れていって、各々大小を比較したりしています。 ファイルの再読込さえできれば、無理に書けば配列を使わなくて済みますが、メモリ消費量等からそこまでは不要と判断しました。 そうですねぇ、縦横を入れ替えて、横方向に計算して行くという手もあるかも知れません。 単純平均なら($i+...+$(i+4))/5とか。 でも、$0で格納すればsplitが要るし、二次元配列にすればメモリが、そうじゃないならやはり先頭からの読み直しが100回程度は必要となるでしょう。 あるいは、縦横入れ替えずに、平均値や回帰分析値をまず横方向に計算していくとか。ただ、デバッグが大変そうなのですが。 SUBSEPは名称を朧気にしか知りませんでしたが、まんま区切りが,か何かで連想配列の機能でも使っているのだろうとは想像しておりました。 メモリ上であんなに膨れあがるのはよく解りません。

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.5

断片的にしか情報がありませんので、Perlにすればいいとか、Rubyならできるとか いうことはいえません。 メモリの消費量の件ですけど、入力ファイル全体を読んで配列に収めるだけ。 ということしたときにどうなりますか? 出力もメモリ上に作って、最後にファイルに書き出すことをしているように読めるので、 入力を溜め込むよりもむしろ出力を保存しているところでメモリを消費しているように思えるのですが。 いずれにしろ #4の方の発言にあるとおりで、処理の全貌が把握できなければ アドバイスも無駄な方向に行く可能性があります。 数値データからなんらかの分析をしたいということだけは検討がつきましたけど。

tekcycle
質問者

お礼

BEGIN{ FS="," while (getline > 0 ){s[i]=$0 ; i=i+1 } for (t=0;t=1;t++){t=0} } の場合、WindowsタスクマネージャーのPF使用量で、496MBだったのが588MBでした。 BEGIN{ FS="," while (getline > 0 ){ for (j=1;j<=240;j++){ s[i,j]=$j } i=i+1 } print "set" for (t=0;t=1;t++){t=0} } の場合、PF使用量496MBだったのが1GBを超え、物理メモリの欄の使用できるメモリが10kBを切った辺りから、CPU使用率が1~2割になり、PF使用量は更に1.5GBを超え、開始から1~2分経っても読み終わりません(プリント文のメッセージが表示されません。) 240の所を20にすると、PF使用量は551MBから716MBになりました。 そういうわけで、$0のまま配列に収納するなら本当に90MB程度の消費で済みそうですが、$1$2...と個別に配列に入れていくとメモリ消費は莫大な物になるようです。 もし最初に読み込ませるなら、$0の状態で読み込ませ、逐次splitの必要がありそうです。 ところで、タイトルのgetlinをもう一度先頭から、というのが、とっても筋の悪い笑える方法でできてしまいました。ご笑覧下さい。 for(略){ for(略){ z=z+1 system ("ren tmp"(z-1)".csv tmp"z".csv") y="sma1mall" z ".csv" while (getline < y > 0){ 処理 } } } 20周させたところですが、配列らしい配列は一つしか使っていないためもあってか、メモリ消費が殆どありません。(まともに動いてなかったりして(笑) ) 最後の方でメモリ消費がどうなっているか判りませんが..。 getline(ファイル名;管理名;開始行;終了行) ってのは将来的にダメでしょうか。 管理名は、同じファイルを読む場合、NF等を別の物にしておく為の物で、 開始行があると、for文でその値を変えてやれば、逆順に読み込ませることもでき、 当然先頭から最後までを繰り返すこともでき、 終了行は、正なら先頭から何行目、負なら最後の行から何行目、という指定方法。 ダメですかね? そもそも今やっているようなプログラムはExcel-VBAで組んだのが最初なのですが(VBAはその時が初めて)、awkのように縦方向の縛りがないのが楽に思えました。

  • a-saitoh
  • ベストアンサー率30% (524/1722)
回答No.4

そもそも何をやりたいか書いてくれないと何のアドバイスもできません。 配列に入れたいというあなたの設計の話ではなく最終目的を書いてください。 なお、1行の要素数が何個であれ、1回最初から最後まで読むだけで個別のフィールドを配列に入れるってのは普通にできますが。。

tekcycle
質問者

お礼

他の方も仰るようにプログラムの仕様を説明できればよいのでしょうが、申し訳ないのですが、そこまで単純ではないようです。 少なくともこちらの説明力は足りていないようです。 一生懸命説明しているつもりではありますが、力不足で申し訳ありません。 ただ、getlineの読み込み行が動かせないということは解りましたし、 そうと決まればどうすれば良いかということで、再度ファイル先頭から読み込ませる方法も思いつきましたし、 まず縦方向ではなく、まず横方向に見ていったらなんてことも考えてみることはできたので、有意義だったと思います。 みなさんどうもありがとうございました。

  • a-saitoh
  • ベストアンサー率30% (524/1722)
回答No.3

そもそも、ファイルを最初から最後まで何回も読むという処理方式を止めるのが正解では? とはいえ、90MBくらいなら配列に入ると思いますが。

tekcycle
質問者

お礼

仰るとおり、もの凄く筋の悪いことをしている可能性はありますが...。(頭の悪いど素人にありがちな...) 配列に入れる場合、 $0が入れば良いのではなく、$1,$2.....$240と各々入れなくてはならないように思います。 ($0で入れて、毎回毎行split()した方が良いのでしょうか?) それとも縦に長~~~いファイル(5万行の2万倍)にして、getline一周にするか....。 雲を掴むような話かとは思いますが良い知恵がありましたらアドバイスお願いいたします。

  • notnot
  • ベストアンサー率47% (4900/10358)
回答No.2

close(filename)すれば次のgetline<filenameはまた最初から読みます。 >これからやろうとしているのはファイルサイズが大きすぎ(.csvで、5万行×240列、90MBです)、 90MBならメモリに入りませんか? 実メモリが小さくてスワップを起こしても、ファイルを毎回読み直すよりはましだと思いますけど。 そもそもどういうことをやりたいか(処理概要)を書けば別の解決があるかもしれません。 あと、ファイルが大きくて処理が複雑ならawkよりPerlやRubyが速いです。

tekcycle
質問者

お礼

そういえばまだそのファイル丸ごとを読み込ませてはいません。 が、 そのファイルもawkで作ったのですが、1/10サイズ(5万行×20列)の物を計算し、結果を二次元配列に入れ、吐き出させたときは、使用メモリは900MBを超していました。(実装メモリは1GBです) 吐き出し過程では1.3GBくらいでした。 (何もしない状態でWindowsのメモリ使用量が450MBくらいです) 膨れあがってしまうので、仕方なしに1/10サイズの物を吐き出させ、後からそれを繋ぎました。 二次元配列が悪いのか、一次元の連想配列にでもすれば良いのか、配列名を1~2文字にでもすればよいのか、よく判りません。 メモリの利用の仕方がPerlやRubyで改善されていて(?)、90MBの3倍程度であれば、配列に全データを入れてもメモリ内に収まり、配列についてfor文で順送りにすれば良いのですが。 やっていることは複雑ではなく、むしろ、私でも書けるほど単純なことで、繰り返し回数が多いというだけだと思います。 PerlやRubyの方が速いでしょうか? 複雑というのは例えばどんなことでしょうか?

関連するQ&A