[Python] split di file di grandi dimensioni

Pietro Battiston toobaz a email.it
Sab 5 Dic 2009 00:32:22 CET


Il giorno ven, 04/12/2009 alle 23.49 +0100, Daniele Varrazzo ha scritto:
> On Fri, 04 Dec 2009 20:16:05 +0100, Pietro Battiston <toobaz a email.it>
> wrote:
> > Il giorno ven, 04/12/2009 alle 19.33 +0100, Daniele Varrazzo ha scritto:
> >> On Fri, 04 Dec 2009 19:05:12 +0100, Pietro Battiston <toobaz a email.it>
> >> wrote:
> >> > Il giorno ven, 04/12/2009 alle 13.39 +0100, Daniele Varrazzo ha
> >> > scritto:
> >> 
> >> >> Io infatti avrei salvato tutti i dizionari dopo un numero prefissato
> >> >> di
> >> >> righe lette dal file di input. In questo modo l'occupazione di
> memoria
> >> è
> >> >> controllata e le prestazioni credo siano in linea (qualche file
> >> potrebbe
> >> >> avere poche righe, ma se su 1000 file aperti si scrivono 1M di righe
> >> >> statisticamente siamo lì). Credo sia anche molto più semplice da
> >> >> scrivere e
> >> >> meno soggetto ad errori.
> >> >> 
> >> > 
> >> > A me sembrerebbe più efficiente una via di mezzo: si scrive su file
> >> > ogni
> >> > volta che si è raggiunta l'occupazione massima di memoria, ma si
> >> > scrive
> >> > solo il file con più righe (da scrivere).
> >> > 
> >> > Significa semplicemente ogni volta che c'è da scrivere fare un max su
> >> > una lista di 1000 elementi (quella delle lunghezze, che può essere
> >> > tranquillamente creata al volo - len ha costo costante, giusto?), e
> non
> >> > mi sembra che possa avere un gran impatto, anche nel caso pessimo in
> >> > cui
> >> > le righe destinate ai diversi file ricorrano con frequenze molto
> >> > simili.
> >> 
> >> Questo non occupa una quantità di memoria costante: potrebbe essere
> >> difficile da controllare (metti se per esempio molti dei file si
> >> "gonfiano"
> >> ma poi restanto a lungo non chiamati in causa: questi resterebbero in
> ram
> >> per non far niente). 
> > 
> > Può essere meglio tenerli in RAM che fare 1000 "microscritture" su
> > disco.
> 
> In che senso "può essere"? A parte che è un'affermazione un po' arbitraria
> (come tutto questo discorso peraltro, ma si fa per giocare) stai
> confrontando due algoritmi paragonando l'occupazione di memoria di uno col
> numero di accessi al disco di un altro: non è comparare le stesse cose.
> 
> Il mio algoritmo (non testato) è
> 
> def split_file(file_name):
>     parts = defaultdict(list)
> 
>     input = iter(open(input_file))
>     try:
>         while 1:
>             # leggi un blocco di record
>             for i in xrange(RECORDS_TO_READ):
>                 record = input.next()
>                 parts[get_filename(record)].append(record)
> 
>             # Scrivi i file letti
>             write_files(parts)
> 
>     # Fine file: scrivi i record che restano
>     except StopIteration:
>         write_files(parts)
> 
> def write_files(parts)
>     # questo consuma il dizionario
>     for filename in parts.keys():
>         open(filename, 'a').write(
>             ''.join(parts.pop(k)))
>     
> Ipotizziamo che ci sono n record ed m file in cui scrivere. Diciamo per
> mettere dei numeri che n=1e9 e m=1e3. In queste condizioni setterei
> RECORDS_TO_READ = 1e6 (= k). Io dico che questo algoritmo ha occupazione di
> memoria limitata ed effettua il minimo numero di scritture in presenza di
> dati normali (ovvero circa n / k * m ~= 1e6). L'occupazione di memoria
> costante è la prima preoccupazione a fronte di un input di grande
> dimensioni.
> 
> 
> >> Ha anche alcune quadraticità (il singolo len è o(1) ma
> >> il max è o(n)).
> > 
> > 'spe, 'spe
> > 
> > Poniamo che in memoria ci stiano anche solo un milione di righe, e che i
> > file siano mille: significa che al caso pessimo, ogni scrittura implica
> > almeno 1000 righe: ovvero, ogni 1000 righe lette/scritte farò 1000
> > chiamate a len ed una chiamata a max.
> > 
> > Certamente non aumenta l'ordine di grandezza, e penso che anzi in
> > termini quantitativi il tempo necessario in più sia trascurabile, perché
> > non c'è paragone tra una chiamata a len e la lettura+scrittura di una
> > riga (e questo è il caso pessimo).
> 
> Potresti scrivere un accenno del tuo algoritmo?

Prima di sprecare (potenzialmente) parole, lo faccio. Poi se c'è
qualcosa che non è chiaro, chiarirò:


def estrai_target(linea):
    # deduci dalla linea in che file deve andare
    ...

def estrai_contenuto(linea):
    # deduci dalla linea cosa va scritto nel file
    ...


class Lettore():
    def __init__(self):
        self.contatore_linee = 0
        self.diz = {}

        file_in = open(file_in)

        while True:
            linea = file_in.readline()
            if linea:
                self.processa_linea(linea)
            else:
                break

        # Finito di parsare il file, facciamo un flush
        for target in self.diz:
            self.scrivi_su_file_e_svuota_lista(target)

        # Finito

    def processa_linea(linea):
       contenuto = estrai_contenuto(linea)
       target = estrai_target(linea)

       if not target in self.diz:
           self.diz[target] = []

       self.diz[target].append(contenuto)
       
       self.contatore_linee += 1

       if self.contatore_linee > MEMORIA_MAX:
           # Si arriva qui al più N_RIGHE * (N_FILE/MEMORIA_MAX) volte
           # (dignitoso purché N_FILE**2 <= MEMORIA_MAX, dato che
           #  le righe seguenti hanno costo grosso modo N_FILE)
           popolarità_massima = 0
           for un_target, sua_lista in zip(self.diz, self.diz.values):
               popolarità = len(sua_lista)
               if popolarità > popolarità_massima:
                   il_più_popolare = un_target
                   popolarità_massima = popolarità

           self.scrivi_su_file_e_svuota_lista(il_più_popolare)       

           self.contatore_linee -= popolarità_massima


# Ovviamente la parte dopo il test con MEMORIA_MAX si può un po'
ottimizzare.

> Ora che ho scritto il mio algoritmo, vedo che non è neanche difficile dire
> "se ci sono pochi record in un file, aspetta prima di scrivere":

Probabilmente con questa aggiunta i nostri metodi si somigliano molto.

ciao

Pietro



Maggiori informazioni sulla lista Python