簡體   English   中英

使用Cassava在內存中加載CSV

[英]Loading a CSV in memory with Cassava

我試圖將CSV加載到內存中作為矢量與木薯的矢量。 我的程序確實有效,但是對於50MB的csv文件使用了大量的內存,我不明白為什么。

我知道使用Data.Csv.Streaming應該對大文件更好,但我認為50MB仍然可以。 我嘗試使用github項目頁面中的或多或少的規范示例來嘗試Data.Csv和Data.Csv.Streaming,我也嘗試實現我自己的解析器輸出Vector of Vector(我的代碼基於attoparsec-csv https:// hackage.haskell.org/package/attoparsec-csv ),所有這些解決方案都使用大約2000MB的內存! 我確信我所做的事情有問題。 這樣做的正確方法是什么?

我的最終目標是將數據完全加載到內存中,以便以后進一步處理。 例如,我可以將數據拆分為有趣的矩陣,並與使用Hmatrix的人一起工作。

以下是我用Cassava嘗試過的2個程序:

1 /使用Data.Csv

import qualified Data.ByteString.Lazy as BL
import qualified Data.Vector as V
import Data.Csv
import Data.Foldable


main = do
   csv <- BL.readFile "train.csv"
   let Right res = decode HasHeader csv :: Either String (V.Vector(V.Vector(BL.ByteString)))
   print $ res V.! 0

2 /使用Data.Csv.Streaming

{-# LANGUAGE BangPatterns #-}

import qualified Data.ByteString.Lazy as BL
import qualified Data.Vector as V
import Data.Csv.Streaming
import Data.Foldable


main = do
   csv <- BL.readFile "train.csv"
   let !a = decode HasHeader csv :: Records(V.Vector(BL.ByteString))
   let !res = V.fromList $ Data.Foldable.toList a
   print $ res V.! 0

請注意,我沒有給你我基於attoparsec-csv制作的程序,因為它與Vector而不是List幾乎完全相同。 該解決方案的內存使用率仍然很低。

有趣的是,在Data.Csv.Streaming解決方案中,如果我只是使用Data.Foldable.for_打印我的數據,那么一切都超快,內存使用量為2MB。 這讓我覺得我的問題與我構建Vector的方式有關。 可能累積thunk而不是將原始數據堆疊成緊湊的數據結構。

謝謝您的幫助,

安托萬

Data.CSVData.CSV.Streaming之間的區別可能不是您所期望的。 第一個產生csv內容的Data.Vector.Vector ,如您所見。 我不確定為什么這個向量的構造應該占用這么多的空間 - 盡管當我反映出由此產生的指針向量指向矢量指向懶惰時,它開始不讓我感到驚訝這里的bytestrings包含28203420個與lazy bytestrings不同的指針,每行371個,每個指向原始字節流的一小部分,通常為'0'。 關注http://blog.johantibell.com/2011/06/memory-footprints-of-some-common-data.html這意味着原始字節流中的典型雙字節序列 - 幾乎所有字節都看起來像這樣:“,0”即。 [44,48] - 由許多指針和構造函數替換:單獨使用lazy bytestring內容會使每對字節占用11個字( ChunkEmpty構造函數用於延遲字節字符串,加上嚴格字節字符串的材料)其中J Tibell提出9個字)...加上原始字節(減去代表逗號和空格的字節)。 在64位系統中,這是一個非常巨大的升級規模。

Data.CSV.Streaming並沒有那么不同:基本上它構造了一個略微裝飾的列表而不是一個向量,所以原則上它可以被懶惰地評估,並且在理想情況下,整個事情不需要在內存中實現,如你注意到了 在這樣的背景下單子,但是,你會,這是不完全保證產生混亂和無秩序“從IO提取列表”。

如果要正確傳輸csv內容,則應使用其中一個流式庫。 (我沒有建議把整個東西放到內存中,除了顯而易見的一個,就是安排cassava將每一行讀成一個很好的緊湊數據類型,而不是一個指向惰性字節串的指針向量;這里雖然我們有371個“字段”)。

所以這是你的程序使用cassava-streams ,它使用cassava(真正的)增量接口,然​​后使用io-streams來創建記錄流:

  {-# LANGUAGE BangPatterns #-}

  import qualified Data.ByteString.Lazy as BL
  import qualified Data.Vector as V
  import Data.Foldable
  import System.IO.Streams (InputStream, OutputStream)
  import qualified System.IO.Streams as Streams
  import qualified System.IO.Streams.Csv as CSV
  import System.IO

  type StreamOfCSV = InputStream (V.Vector(BL.ByteString))

  main = withFile "train.csv" ReadMode $ \h -> do
     input          <- Streams.handleToInputStream h 
     raw_csv_stream <- CSV.decodeStream HasHeader input
     csv_stream     <- CSV.onlyValidRecords raw_csv_stream :: IO StreamOfCSV
     m <- Streams.read csv_stream
     print m

這樣就可以立即使用比hello-world更多的內存,打印第一條記錄。 您可以在教程源代碼中看到更多操作https://github.com/pjones/cassava-streams/blob/master/src/System/IO/Streams/Csv/Tutorial.hs其他流媒體庫也有類似的庫。 如果您想要構建的數據結構(如矩陣)可以適合內存,您應該能夠通過使用Streams.fold折疊線來構造它,如果您嘗試從中提取的信息應該沒有問題在折疊操作消耗之前,每條線都會被正確評估。 如果你可以安排木薯輸出帶有未裝箱字段的非遞歸數據結構,那么就可以為該類型編寫一個Unbox實例,並將整個csv折疊成一個緊密包裝的未裝箱的矢量。 在這種情況下,每行有371個不同的字段,因此我猜不是一個選項。

以下是Data.CSV.Streaming程序的等效項:

  main = withFile "train.csv" ReadMode $ \h -> do
    input          <- Streams.handleToInputStream h 
    raw_csv_stream <- CSV.decodeStream HasHeader input
    csv_stream     <- CSV.onlyValidRecords raw_csv_stream :: IO StreamOfCSV
    csvs <- Streams.toList csv_stream
    print (csvs !! 0)

它有同樣的麻煩,因為它在嘗試找出第一個元素之前使用Streams.toList來收集巨大的列表。

- 附錄

這里的值得的是一個pipes-csv變體,它只是將每個解析后的行壓縮成一個未裝箱的Int s向量(這比找到Doubles更容易,這就是這個csv實際存儲的內容,使用來自bytestring包的readInt 。 )

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.Csv

import qualified Pipes.Prelude as P
import qualified Pipes.ByteString as Bytes
import Pipes
import qualified Pipes.Csv as Csv
import System.IO
import Control.Applicative

import qualified Control.Foldl as L

main = withFile "train.csv" ReadMode $ \h -> do
  let csvs :: Producer (V.Vector ByteString) IO ()
      csvs = Csv.decode HasHeader (Bytes.fromHandle h) >-> P.concat
      -- shamelessly reading integral part only, counting bad parses as 0
      simplify bs = case B.readInt bs of
        Nothing       -> 0
        Just (n, bs') -> n
      uvectors :: Producer (U.Vector Int) IO ()
      uvectors = csvs  >-> P.map (V.map simplify) >-> P.map (V.foldr U.cons U.empty)
  runEffect $ uvectors >-> P.print

您可以使用foldl庫中的折疊折疊行,或者通過更換最后一行來解決這些行

  let myfolds = liftA3 (,,) (L.generalize (L.index 13))   -- the thirteenth row, if it exists
                            (L.randomN 3)   -- three random rows
                            (L.generalize L.length) -- number of rows
  (thirteen,mvs,len) <- L.impurely P.foldM myfolds uvectors

  case mvs of 
    Nothing -> return ()
    Just vs -> print (vs :: V.Vector (U.Vector Int))
  print thirteen
  print len

在這種情況下,我正在收集第十三行,三條隨機行和記錄總數 - 任何數量的其他折疊都可以與這些相結合。 特別是,我們也可以使用L.vector將所有行收集到一個巨大的向量中,考慮到這個csv文件的大小,這可能仍然是一個壞主意。 下面我們回到起點,我們收集所有內容並打印完成的矢量矢量的第17行,即一種大矩陣。

  vec_vec <- L.impurely P.foldM  L.vector uvectors
  print $ (vec_vec :: V.Vector (U.Vector Int)) V.! 17

這需要大量的內存,但並不會給我的小筆記本電腦帶來特別的壓力。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM