haskell-jp / general #12

呼ぶ側では、単純に結果のheadのlengthを評価するだけでテストしていたんですが、単純に出力するだけのものを試してみます。
あ、これ戻り値IOだから遅延しないか
いやparseCSVTはIOじゃないから遅延しますね
や、パース失敗かどうか最後まで入力読まないと駄目だから遅延しないです。だとmapM_でも遅延にならないですね
ちょっと混乱したので見なかった事に…
行の分割を lines 関数に任せて1行ごとにパーサーを走らせれば回避できる?
プロファイルの先頭部分ですが,quated Cell が悪さをしているらしくいじってみましたがどこが原因がなかなかわかりません.
lines関数も試させていただきます, 環境がネットに繋がっていないので(現在自前にうつしています)反応遅くすみません.
lines関数で行ごとに区切って読む方が軽いのは間違いなさそうですね。そうすると、遅延IOにより、最初の要素にアクセスしただけでは最終行まで読まないので注意です
strict 拡張をしていても遅延が起きるでしょうか?
おそらく起きます。Strict拡張やBangパターンはあくまでseq相当で、WHNFまでしか評価しないので。deepseqすれば最後まで行くはずです
問題は なにを deepseqするかなんですが…:thinking_face:
ありがとうございます. 今,putStrLnで試していますが,linesをその後に試させていただきます.
少し話題がずれるのですが,1GB程度のファイルをText.IO.readFilesしただけでも5~6GB消費するのは,Boxedだとしかたないのでしょうか?
readCSVTWin path >>= \x -> deepseq x (return ())) とかですかね>何を~
total allocだからreallocして領域広げた分を累計してるのかも(未確認)
すみません,計算待ちです,6GBのファイルを読ませてみていますが,現状121GB使っているので,呼ぶ側ではなさそうです.
conduitなどを使う方がいいでしょうね。
不勉強でconduitをあまり理解していないのですが,可能なことは逐次処理で行別にパースしていくイメージであっているでしょうか?
行単位でもできますし、調整すればセル単位でもできます
現在linesで先に分割したものをためしてみているます.評価をputSTRLnにしたままだったので,出力に時間をくっていますが,40GB程度の消費で終わりそうです.
一応,当面の課題は解決しました.ありがとうございます.一点疑問なのですが,6GBのcsvを[[Text]]で保持して,40GBほどメモリを消費するというのはHaskellの相場からすると通常なのでしょうか?conduit等も試させていただきますが,メモリ的にも節約が見込めるものでしょうか?
- Total allocなので、allocしてすぐ解放したような物もカウントされているのでは?
- HaskellというかParsecが遅い

あたりも考えられるので、詳しくは検証しないと分からない所です。メモリ使用量はともかく速度については、リークや文字列処理に気を遣って書いたHaskellはC++の5倍程度遅く、JVMロード時間を除いたJavaとトントンくらい、とされています。
Total allocよりも、Total memory in useを見るべきか。あとはグラフ化した奴も見れば色々分かるかもしれません
メモリに関しては載せていませんでしたが(すみません)単純にタスクマネージャーで見ていたの実行時の消費です。速度に関しては、並列化でどうにか頑張れると思うのですが、メモリ消費を減らす知見があれば教えていただけると大変助かります。要求ばかりですみません。
ただ、認識不足でしたが一度に読まずにバッチ処理をしろという意味だと思いますので(>>conduit )そちらで努力してみます。
ヒーププロファイリングのグラフ化は行ったことがなかったので今後利用させていただきます。ご教示助かります。ありがとうございました。
conduitの敷居が高ければ、Lazy TextでgetContentsする->Lazyのlinesで分割->toStrict->パース でも一応大丈夫ですね。あるいは他言語同様にgetLineでループして頑張るか
なるほど。遅延評価の活かし方参考になります。せっかくなのでstreamも勉強してみますが、当面の実装はそちらで試してみます。使い始めたものの全く扱えていないので大変勉強になります。
データの使われ方によって対策が変わると思います。前から順にストリーム処理可能ならcassavaなどのstreamingインタフェースを使えば良さそうですが、全てのデータをメモリ上に保持してランダムアクセスする場合は[[Text]]ではなくもっとコンパクトなデータに変換するのが良いと思います。データが数値なら適切な数値型にしてunboxed vectorにするなどです。
リストはかなり富豪的なデータ構造で大量の要素を保持しなければならない時にはメモリ使用量やGCの仕事量が増えて遅くなりがちなので気をつけないといけません
ご回答ありがとうございます.日本語混じりの文字列なので,数値型やBytestringなどは使えません.Unboxed vectorは,そのままTextを要素にできないように理解していましたが,
data NewText = NewText {text :: {-# Unpack #-} Text のようなものをBoxedVectorに入れても,同じ効果が得られるものでしょうか.
newtype にはUnpackプラグマは使えないのですね..修正しました
それと気になるのが,Parserのような処理で返り値をVectorにするとConsを各Char(をT.pack)したものに繰り返すことになりそうですが,そこが少し怖いです.以前,手当たり次第にVectorにしていて,snoc,cons,++あたりを繰り返して死んだことがあり,寧ろリストの方が安全ではないかと考えていました. いずれにしても手元で試させていただきます.
勉強のために色々試してみたのですが、以下のように変更したら total memory in use が1/3ぐらいになりました。

cell = (quotedCell <|> many (noneOf ",\n\r")) >>= (\res -> return $! T.pack res)
これ意識して避けるの絶対無理ですね…… Textでゴリゴリやりたい人はattoparsec使えって事なんでしょうか
megaparsecとかtrifectaならまた違ったりするのかな。 :thinking_face:
そもそもparsecはincremental parsingに対応していなかったと思うので、大きなデータを使う場合はattoparsecを使ってくださいということだと思います
megaparsec でも書き直してみましたが、同様でした。attoparsec なら違うかもしれないですね。
attoparsecは"Use the Text-oriented parsers whenever possible" に従えば大丈夫そう? 
あと >>= で書いてある部分を do で書き換えて、全部の関数に型を明示的に書いたら、処理的には何も変更していませんが 50MB ぐらい減りましたね。
Strict拡張があるので、doで変数束縛すると暗黙にseqが掛かるからそれじゃないですかね
確かに手元で確認したら Strict 拡張の有無でプロファイル結果が変わったので、それっぽいです。
ここまでくると途中経過のソース全部含めてBIG MOONの記事になっていて欲しくなりますね :pray:
書いてみますね!
@akagi15 念のため確認ですが、ソースを @wado さんのブログに載せちゃっても大丈夫でしょうか。。。?
@igrep 問題ありません。皆さん色々議論してくださって大変勉強になりましたので是非残してください。
お知らせです、MiniKanren やReasoned Schemer で知られるWilliam Byrd 氏(日本名:ウィルちゃん) が12月17日に茅場町でTalkされます。Abstractなどは後ほど出します^_^ https://www.meetup.com/ja-JP/Tokyo-Haskell-Meetup/events/ckxnrpyxqbvb/
すみませんが、このチャンネルは相談か、このSlack Workspace自体についての連絡用に限定したいので、 event-announcementrandom に移していただけないでしょうか? :bow:
@yukimemi has joined the channel