[Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda kaspar » 21/12/2020, 19:41

Ciao :smt039
Uso readFile abbastanza spensieratamente (tipicamente ne spezzo il risultato in linee, filtro, mappo, blb bla...). Tuttavia mi chiedo se abuso, sto fraintendendo la lazyness di Haskell. Un esempio pratico semplice semplice per spiegarmi un po' meglio.

Problema. Si ha un file "num.txt" che contiene un numero per riga (rigorosamente): definire una funzione che dica la somma dei dati lì dentro.

Codice:
-- sandbox.hs

import Control.Conditional (ifM)
import System.IO (Handle, IOMode(..), hGetLine, hIsEOF, withFile)

-- one line version
sumFile1 :: FilePath -> IO Double
sumFile1 f = fmap (sum . map read . lines) (readFile f)

-- and a Lisp-like approach
sumFile2 :: FilePath -> IO Double
sumFile2 f = withFile f ReadMode (_sum 0)
  where
    _sum :: Double -> Handle -> IO Double
    _sum s hdl = ifM (hIsEOF hdl) (return s) $ do
        x <- fmap read (hGetLine hdl)
        _sum (s+x) hdl


In termini di velocità e spazio la seconda implementazione mi sembra migliore, però Haskell è lazy e quindi potrebbe non cambiare niente... :-k
Ultima modifica di kaspar il 21/12/2020, 21:57, modificato 1 volta in totale.
kaspar
Junior Member
Junior Member
 
Messaggio: 157 di 495
Iscritto il: 17/11/2019, 09:58

Re: [Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda solaàl » 21/12/2020, 20:35

Beh, puoi controllare chi è piu veloce, no? C'è un comando time in ghc, o qualcosa del genere...
"In verità le cose che nella vita sono tenute in gran conto si riducono a vanità, o putredine di nessun valore; botoli che si addentano, bambocci litigiosi che ora ridono, poi tosto piangono." (Lotario conte di Segni)
Avatar utente
solaàl
Senior Member
Senior Member
 
Messaggio: 711 di 1672
Iscritto il: 31/10/2019, 01:45

Re: [Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda kaspar » 22/12/2020, 09:48

Ci ho già provato per quel mi concerne: ho generato un file con tipo \(3000\) righe di numeri a caso

Codice:
~/sandbox$ for i in {1..3000}; do echo $RANDOM >> num.ls; done


E ho provato entrambe le funzioni con time: non sono un esperto, ma le voci "real", "user" e "sys" mi sembravano comparabili, nel senso che su più test una volta la prima era leggermente più rapida dell'altra, un volta la seconda più della prima. Solo che: \(3000\) righe sono poche? poi come faccio ad ottenere un analisi di spazio "consumato"?
kaspar
Junior Member
Junior Member
 
Messaggio: 159 di 495
Iscritto il: 17/11/2019, 09:58

Re: [Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda apatriarca » 22/12/2020, 17:40

Il mio consiglio è quello di usare le stesse funzioni, ma rese disponibili dal modulo Data.ByteString.Lazy.Char8 (devi importarlo qualified). Dovrebbe essere più efficiente rispetto all'uso di stringhe come nel tuo caso.

Credo che nel tuo caso le due funzioni siano più o meno equivalenti. Comunque puoi leggere come fare il profiling di un codice haskell per esempio qui.
apatriarca
Moderatore
Moderatore
 
Messaggio: 5520 di 10436
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda apatriarca » 22/12/2020, 18:11

Inoltre dovresti aumentare tanto il numero di righe… \(3000\) numeri sono infatti molto pochi. Se vuoi vedere differenze inizia a passare a qualche milione.
apatriarca
Moderatore
Moderatore
 
Messaggio: 5522 di 10436
Iscritto il: 08/12/2008, 20:37
Località: Madrid

Re: [Haskell] readFile: in termini di performance e spazio cos'è meglio?

Messaggioda kaspar » 02/01/2021, 01:25

Ciao.
Allora sono andato a informarmi un po' sul profiling. Ho fatto dei tentativi su file num.ls di \(3 \cdot 10^6\) righe, che possono essere poche, ma bastano a trarre delle conclusioni.
1. Tra sumFile1 e sumFile2 il confronto è impari: andando a vedere come sono implementate le funzioni di Prelude, sum è strict, mentre nella seconda ci sono dei pezzi pigri.
2. La seconda implementazione è più lenta rispetto alla prima, ma di poco.
3. Sulla seconda versione ho più controllo sul processo di valutazione, e quindi sulla memoria.
Proviamo così allora:
Codice:
-- sandbox.hs

{-# LANGUAGE BangPatterns #-}

import Control.Conditional (ifM)
import System.IO (Handle, hGetLine, hIsEOF, IOMode(..), withFile)

main :: IO ()
main = print =<< sumFile "num.ls"

sumFile :: FilePath -> IO Double
sumFile f = withFile f ReadMode (_sum 0)
  where
    _sum :: Double -> Handle -> IO Double
    _sum !s hdl = ifM (hIsEOF hdl) (return s) $ do
        x <- (fmap read . hGetLine) hdl
        _sum (s+x) hdl


Codice:
$ ghc -O -rtsopts sandbox.hs
$ for i in {1..3000000}; do echo $RANDOM >> num.ls; done
$ ./sandbox +RTS -sstderr
4.9164487364e10
  16,747,949,904 bytes allocated in the heap
      11,267,488 bytes copied during GC
          53,592 bytes maximum residency (3 sample(s))
          36,520 bytes maximum slop
               0 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0     16060 colls,     0 par    0.087s   0.086s     0.0000s    0.0001s
  Gen  1         3 colls,     0 par    0.001s   0.001s     0.0002s    0.0004s

  INIT    time    0.000s  (  0.000s elapsed)
  MUT     time    5.378s  (  5.411s elapsed)
  GC      time    0.088s  (  0.086s elapsed)
  EXIT    time    0.000s  (  0.000s elapsed)
  Total   time    5.466s  (  5.497s elapsed)

  %GC     time       0.0%  (0.0% elapsed)

  Alloc rate    3,114,094,493 bytes per MUT second

  Productivity  98.4% of total user, 98.4% of total elapsed

Ho azzerato lo spazio consumato, ridotto ancora di più i tempi rispetto a sumFile1 e la "productivity" si avvicina al 100% (mentre con sumFile1 si sta al 91%). È tutta questione di strictness allora.

Per quanto riguarda ByteString: so che esiste e non è difficile passarci, ma la cosa era un esercizietto.
kaspar
Junior Member
Junior Member
 
Messaggio: 160 di 495
Iscritto il: 17/11/2019, 09:58


Torna a Informatica

Chi c’è in linea

Visitano il forum: Nessuno e 1 ospite