[英]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.CSV
和Data.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個字( Chunk
和Empty
構造函數用於延遲字節字符串,加上嚴格字節字符串的材料)其中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.