web-dev-qa-db-fra.com

Traite les fichiers texte très volumineux (> 20 Go) ligne par ligne

J'ai un certain nombre de très gros fichiers texte que je dois traiter, le plus gros étant d'environ 60 Go.

Chaque ligne a 54 caractères dans sept champs et je veux supprimer les trois derniers caractères de chacun des trois premiers champs - ce qui devrait réduire la taille du fichier d'environ 20%.

Je suis tout nouveau à Python et j'ai un code qui fera ce que je veux faire à environ 3,4 Go par heure, mais pour être un exercice valable, j'ai vraiment besoin d'obtenir au moins 10 Go/h - existe-t-il un moyen d'accélérer ce processus?

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:
        x = l.split(' ')[0]
        y = l.split(' ')[1]
        z = l.split(' ')[2]
        w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
        l = r.readline()
    r.close()
    w.close()

Toute aide sera grandement appréciée. J'utilise l'IDLE Python GUI sur Windows 7 et j'ai 16 Go de mémoire - peut-être qu'un système d'exploitation différent serait plus efficace?.

Edit: Voici un extrait du fichier à traiter.

70700.642014 31207.277115 -0.054123 -1585 255 255 255
70512.301468 31227.990799 -0.255600 -1655 155 158 158
70515.727097 31223.828659 -0.066727 -1734 191 187 180
70566.756699 31217.065598 -0.205673 -1727 254 255 255
70566.695938 31218.030807 -0.047928 -1689 249 251 249
70536.117874 31227.837662 -0.033096 -1548 251 252 252
70536.773270 31212.970322 -0.115891 -1434 155 158 163
70533.530777 31215.270828 -0.154770 -1550 148 152 156
70533.555923 31215.341599 -0.138809 -1480 150 154 158
40
Tom_b

C'est plus idiomatique d'écrire votre code comme ça

def ProcessLargeTextFile():
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

L'économie principale ici est de ne faire que le split une fois, mais si le CPU n'est pas taxé, cela ne fera probablement que très peu de différence

Il peut aider à économiser jusqu'à quelques milliers de lignes à la fois et à les écrire en un seul coup pour réduire la saturation de votre disque dur. Un million de lignes, c'est seulement 54 Mo de RAM!

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)

proposé par @Janne, une manière alternative de générer les lignes

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z, rest = line.split(' ', 3)
            bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest)))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)
26
John La Rooy

Mesure! Vous avez obtenu quelques conseils utiles pour améliorer votre code python et je suis d'accord avec eux. Mais vous devez d'abord déterminer quel est votre véritable problème. Mes premières étapes pour trouver votre goulot d'étranglement seraient:

  • Supprimez tout traitement de votre code. Il suffit de lire et d'écrire les données et de mesurer la vitesse. Si la lecture et l'écriture des fichiers est trop lente, ce n'est pas un problème de code.
  • Si la lecture et l'écriture sont déjà lentes, essayez d'utiliser plusieurs disques. Vous lisez et écrivez en même temps. Sur le même disque? Si oui, essayez d'utiliser différents disques et réessayez.
  • Une bibliothèque io asynchrone (Twisted?) Pourrait également aider.

Si vous avez trouvé le problème exact, demandez à nouveau des optimisations de ce problème.

12
Achim

Je vais ajouter cette réponse pour expliquer pourquoi la mise en mémoire tampon a du sens et offre également une autre solution

Vous obtenez des performances à couper le souffle. Cet article Est-il possible d'accélérer python IO? montre qu'une lecture de 10 Go devrait prendre environ 3 minutes. L'écriture séquentielle a la même vitesse. Il vous manque donc un facteur de 30 et votre objectif de performances est encore 10 fois plus lent que ce qui devrait être possible.

Ce genre de disparité réside presque certainement dans le nombre de têtes recherchées par le disque. Une recherche de tête prend des millisecondes. Une seule recherche correspond à plusieurs mégaoctets de lecture-écriture séquentielle. Extrêmement cher. Les opérations de copie sur le même disque nécessitent une recherche entre l'entrée et la sortie. Comme cela a été indiqué, une façon de réduire les recherches consiste à mettre en mémoire tampon de manière à ce que de nombreux mégaoctets soient lus avant d’écrire sur le disque et vice versa. Si vous pouvez convaincre le système python io de le faire, c'est parfait. Sinon, vous pouvez lire et traiter des lignes dans un tableau de chaînes, puis écrire après que peut-être 50 Mo de sortie sont prêts. Cette taille signifie qu'un La recherche induira une baisse de performances <10% par rapport au transfert de données lui-même.

L'autre moyen très simple d'éliminer les recherches entre les fichiers d'entrée et de sortie est d'utiliser une machine avec deux disques physiques et des canaux io entièrement séparés pour chacun. Entrée d'un. Sortie vers un autre. Si vous faites beaucoup de transformations de gros fichiers, il est bon d'avoir une machine avec cette fonctionnalité.

7
Gene

Comme vous ne semblez pas être limité par le CPU, mais plutôt par les E/S, avez-vous essayé avec quelques variations sur le troisième paramètre de open?

En effet, ce troisième paramètre peut être utilisé pour donner la taille du buffer à utiliser pour les opérations de fichiers!

L'écriture simple de open( "filepath", "r", 16777216 ) utilisera des tampons de 16 Mo lors de la lecture du fichier. Ça doit aider.

Utilisez la même chose pour le fichier de sortie et mesurez/comparez avec un fichier identique pour le reste.

Remarque: Il s'agit du même type d'optimisation suggéré par d'autres, mais vous pouvez le gagner ici gratuitement, sans changer votre code, sans avoir à vous mettre en mémoire tampon.

7
Didier Trosset
ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:

Comme cela a déjà été suggéré, vous pouvez utiliser une boucle for pour rendre cela plus optimal.

    x = l.split(' ')[0]
    y = l.split(' ')[1]
    z = l.split(' ')[2]

Vous effectuez une opération de division 3 fois ici, en fonction de la taille de chaque ligne, cela aura un impact sur les performances. Vous devez diviser une fois et affecter x, y, z aux entrées du tableau qui revient.

    w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

À chaque ligne que vous lisez, vous écrivez immédiatement dans le fichier, qui est très gourmand en E/S. Vous devez envisager de mettre votre sortie en mémoire tampon et de la pousser régulièrement sur le disque. Quelque chose comme ça:

BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    buf = ""
    bufLines = 0
    for lineIn in r:

        x, y, z = lineIn.split(' ')[:3]
        lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])
        bufLines+=1

        if bufLines >= BUFFER_SIZE:
            # Flush buffer to disk
            w.write(buf)
            buf = ""
            bufLines=1

        buf += lineOut + "\n"

    # Flush remaining buffer to disk
    w.write(buf)
    buf.close()
    r.close()
    w.close()

Vous pouvez modifier BUFFER_SIZE pour déterminer un équilibre optimal entre l'utilisation de la mémoire et la vitesse.

4
seanhodges

Votre code est plutôt non idiomatique et fait beaucoup plus d'appels de fonction que nécessaire. Une version plus simple est:

ProcessLargeTextFile():
    with open("filepath") as r, open("output") as w:
        for line in r:
            fields = line.split(' ')
            fields[0:2] = [fields[0][:-3], 
                           fields[1][:-3],
                           fields[2][:-3]]
            w.write(' '.join(fields))

et je ne connais pas de système de fichiers moderne qui soit plus lent que Windows. Puisqu'il semble que vous utilisiez ces énormes fichiers de données comme bases de données, avez-vous envisagé d'utiliser une vraie base de données?

Enfin, si vous souhaitez simplement réduire la taille des fichiers, avez-vous envisagé de compresser/compresser les fichiers?

3
msw

Ceux-ci semblent être de très gros fichiers ... Pourquoi sont-ils si gros? Quel traitement faites-vous par ligne? Pourquoi ne pas utiliser une base de données avec une carte pour réduire les appels (le cas échéant) ou de simples opérations sur les données? Le but d'une base de données est d'abstraire la gestion et la gestion de grandes quantités de données qui ne peuvent pas toutes tenir en mémoire.

Vous pouvez commencer à jouer avec l'idée avec sqlite qui utilise simplement des fichiers plats comme bases de données. Si vous trouvez l'idée utile, passez à quelque chose d'un peu plus robuste et polyvalent comme postgresql .

Créer une base de données

 conn = sqlite3.connect('pts.db')
 c = conn.cursor()

Crée une table

c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')

Utilisez ensuite l'un des algorithmes ci-dessus pour insérer toutes les lignes et tous les points de la base de données en appelant

c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")

Maintenant, comment vous l'utilisez dépend de ce que vous voulez faire. Par exemple, pour travailler avec tous les points d'un fichier en faisant une requête

c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")

Et obtenez n lignes à la fois à partir de cette requête avec

c.fetchmany(size=n)

Je suis sûr qu'il existe un meilleur wrapper pour les instructions sql quelque part, mais vous avez l'idée.

2
craastad

Puisque vous ne mentionnez que l’économie d’espace comme avantage, y a-t-il une raison pour laquelle vous ne pouvez pas simplement stocker les fichiers compressés au format gzip? Cela devrait permettre d'économiser 70% et plus sur ces données. Ou pensez à obtenir NTFS pour compresser les fichiers si l'accès aléatoire est toujours important. Vous obtiendrez des économies beaucoup plus importantes sur le temps d'E/S après l'un ou l'autre.

Plus important encore, où sont vos données que vous n'obtenez que 3,4 Go/h? C'est autour des vitesses USBv1.

2
jthill

Voici le code pour charger des fichiers texte de n'importe quelle taille sans causer de problèmes de mémoire. Il prend en charge des fichiers de taille gigaoctet. Il fonctionnera sans problème sur tout type de machine, il vous suffit de configurer CHUNK_SIZE en fonction de la RAM de votre système. Plus le CHUNK_SIZE, plus seront les données lues à la fois

https://Gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

téléchargez le fichier data_loading_utils.py et importez-le dans votre code

usage

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(line, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=process_lines)

La méthode process_lines est la fonction de rappel. Il sera appelé pour toutes les lignes, avec la ligne de paramètre représentant une seule ligne du fichier à la fois.

Vous pouvez configurer la variable CHUNK_SIZE en fonction des configurations matérielles de votre machine.

1
Iyvin Jose

Vous pouvez essayer d'enregistrer votre résultat fractionné en premier et non pas à chaque fois que vous avez besoin d'un champ. Peut-être que cela va accélérer.

vous pouvez également essayer de ne pas l'exécuter dans gui. Exécutez-le en cmd.

1
Muetze

Lisez le fichier en utilisant for l in r: pour bénéficier de la mise en mémoire tampon.

1
Janne Karila