2014-11-03 4 views
2

Я пытаюсь прочитать большой файл csv по haskell и генерировать количество слов по каждому столбцу.Как читать большой файл csv?

Это больше, чем 4M строк в файле.

Итак, я выбираю блок и каждый раз подсчитываю количество слов (5k строк на один блок). И чем суммировать это вместе.

Когда я тестирую функцию с 12000 строк и 120000 строк, время увеличивается почти линейно. Но когда прочитано 180000 строк, время выполнения превышает более четырех раз.

Я думаю, это потому, что памяти недостаточно, обмен с диском делает функцию намного медленнее.

Я написал свой код как стиль map/reduce, но как заставить haskell не хранить все данные в памяти?

Удар - это мой код и результат профилирования.

import Data.Ord 
import Text.CSV.Lazy.String 
import Data.List 
import System.IO 
import Data.Function (on) 
import System.Environment 

splitLength = 5000 


mySplit' [] = [] 
mySplit' xs = [x] ++ mySplit' t 
    where 
    x = take splitLength xs 
    t = drop splitLength xs     

getBlockCount::Ord a => [[a]] -> [[(a,Int)]] 
getBlockCount t = map 
    (map (\x -> ((head x),length x))) $ 
    map group $ map sort $ transpose t 

foldData::Ord a=> [(a,Int)]->[(a,Int)]->[(a,Int)] 
foldData lxs rxs = map combind wlist 
    where 
     wlist = groupBy ((==) `on` fst) $ sortBy (comparing fst) $ lxs ++ rxs 
     combind xs 
     | 1==(length xs) = head xs 
     | 2 ==(length xs) = (((fst . head) xs), ((snd . head) xs)+((snd . last) xs)) 


loadTestData datalen = do 
    testFile <- readFile "data/test_csv" 
    let cfile = fromCSVTable $ csvTable $ parseCSV testFile 
    let column = head cfile 
    let body = take datalen $ tail cfile 
    let countData = foldl1' (zipWith foldData) $ map getBlockCount $ mySplit' body 
    let output = zip column $ map (reverse . sortBy (comparing snd)) countData 
    appendFile "testdata" $ foldl1 (\x y -> x ++"\n"++y)$ map show $tail output 

main = do 
    s<-getArgs 
    loadTestData $ read $ last s 

профилирование результат

loadData +RTS -p -RTS 12000 

total time =  1.02 secs (1025 ticks @ 1000 us, 1 processor) 
total alloc = 991,266,560 bytes (excludes profiling overheads) 

loadData +RTS -p -RTS 120000 

total time =  17.28 secs (17284 ticks @ 1000 us, 1 processor) 
total alloc = 9,202,259,064 bytes (excludes profiling overheads) 



    loadData +RTS -p -RTS 180000 

total time =  85.06 secs (85059 ticks @ 1000 us, 1 processor) 
total alloc = 13,760,818,848 bytes (excludes profiling overheads) 
+1

Вам необходимо использовать поточную библиотеку, например 'csv-conduit' или' pipes-csv' – ErikR

ответ

-2

Я имел эту проблему, прежде чем на другом языке. Хитрость заключается не в том, чтобы читать данные в памяти, а просто читать ее в одной строке за раз. Когда вы читаете следующую строку, просто переписывайте переменные, поскольку вы ищете только количество слов. Просто проверьте условие окончания файла EOF в вашем потоке io и затем выйдите. Таким образом, вы не должны разбить файл.

Надеюсь, что поможет

+1

«Когда вы читаете следующую строку, просто переписывайте переменные, поскольку вы ищете только число слов». Это Хаскелл. У нас нет «переменных». – alternative

10

Итак, во-первых, несколько предложений.

  1. Списки не бывают быстрыми. Хорошо, хорошо, минус - постоянное время, но в целом списки не быстрые. Вы используете списки. (Data.Sequence было бы быстрее для двухконтактных cons и потребления)

  2. Строки медленные. Строки медленны, потому что они [Char] (List of Char). Библиотека, которую вы сейчас используете, записывается в виде списков строк. Обычно привязанные списки связанных списков символов не предназначены для обработки текста. Это не буено. Используйте Text (for, uh, text) или ByteString (для байтов) вместо String в будущем, если это не что-то маленькое, а не чувствительное к производительности.

  3. Библиотека, которую вы используете, просто ленив, а не потоковая передача. Вам придется обрабатывать поведение потоковой передачи, наложенное на ленивую семантику, чтобы получить постоянное использование памяти. Потоковые библиотеки решают проблему постепенной обработки данных и ограничения использования памяти. Я бы предложил изучить Трубы или Канал для этого общего класса проблем. В некоторых проблемных библиотеках также предлагается API-интерфейс iteratee, который может использоваться для потоковой передачи. API Iteratee можно использовать напрямую или подключить к Pipes/Conduit/etc.

Я не думаю, что библиотека, которую вы используете, является хорошей идеей.

Я предлагаю вам использовать один из следующих библиотек:

http://hackage.haskell.org/package/pipes-csv (трубы на основе)

https://hackage.haskell.org/package/cassava-0.4.2.0/docs/Data-Csv-Streaming.html (библиотека Common CSV, а не на основе конкретной библиотеки потокового)

https://hackage.haskell.org/package/csv-conduit (на основе Conduit)

Они должны обеспечивать хорошую производительность и постоянную память, используя все, что вы можете накапливать.

1

Есть несколько вещей, чтобы быть в курсе:

  1. Вы хотите, чтобы поток данных таким образом, что вы только держать в памяти небольшую часть входного файла в любое время. Возможно, вы сможете это сделать с ленивым IO и пакетом lazy-csv. Тем не менее, по-прежнему легко ненадлежащим образом удерживать ссылки, которые сохраняют весь ваш вход в память. Лучшим вариантом является использование потоковой библиотеки, такой как csv-conduit или pipes-csv.

  2. Используйте ByteString или Text при обработке большого количества строковых данных.

  3. Вы хотите, чтобы использовать строгие операции при уменьшении ваших данных. В противном случае вы будете просто наращивать громкость неоценимых выражений в памяти до самого конца, когда вы распечатываете результат. Одно место, где можно было бы создавать громы, - это ваша функция foldData - выражения счетчика слов, похоже, не уменьшаются.

Ниже приведен пример программы, которая будет вычислять общую длину всех слов в каждой колонке файла CSV и делает его в постоянной памяти. Основные особенности:

  • использует ленивый IO
  • использует lazy-csv пакет с (ленивый) ByteString вместо String
  • использует BangPatterns для strictify вычисления числа строк
  • использует Unboxed массив держать счетчики столбцов

код:

{-# LANGUAGE BangPatterns #-} 

import qualified Data.ByteString.Lazy.Char8 as BS 
import Data.ByteString.Lazy (ByteString) 
import Text.CSV.Lazy.ByteString 
import System.Environment (getArgs) 
import Data.List (foldl') 
import Data.Int 
import Data.Array.IO 
import Data.Array.Unboxed 
import Control.Monad 

type Length = Int64 -- use Int on 32-bit systems 

main = do 
    (arg:_) <- getArgs 
    (line1:lns) <- fmap BS.lines $ BS.readFile arg 

    -- line1 contains the header 
    let (headers:_) = [ map csvFieldContent r | r <- csvTable (parseCSV line1) ] 
     ncols = length headers :: Int 

    arr <- newArray (1,ncols) 0 :: IO (IOUArray Int Length) 
    let inc i a = do v <- readArray arr i; writeArray arr i (v+a) 

    let loop !n [] = return n 
     loop !n (b:bs) = do 
     let lengths = map BS.length $ head [ map csvFieldContent r | r <- csvTable (parseCSV b) ] 
     forM_ (zip [1..] lengths) $ \(i,a) -> inc i a 
     loop (n+1) bs 
    print headers 
    n <- loop 0 lns 
    putStrLn $ "n = " ++ show (n :: Int) 
    arr' <- freeze arr :: IO (UArray Int Length) 
    putStrLn $ "totals = " ++ show arr' 
Смежные вопросы