J'utilise Python 2.6 sur un Mac Mini avec 1 Go de RAM. Je veux lire dans un énorme fichier texte
$ ls -l links.csv; file links.csv; tail links.csv
-rw-r--r-- 1 user user 469904280 30 Nov 22:42 links.csv
links.csv: ASCII text, with CRLF line terminators
4757187,59883
4757187,99822
4757187,66546
4757187,638452
4757187,4627959
4757187,312826
4757187,6143
4757187,6141
4757187,3081726
4757187,58197
Ainsi, chaque ligne du fichier consiste en un tuple de deux valeurs entières séparées par des virgules. Je souhaite lire le fichier entier et le trier en fonction de la deuxième colonne. Je sais que je pourrais faire le tri sans lire le fichier entier en mémoire. Mais je pensais que pour un fichier de 500 Mo, je devrais pouvoir le faire en mémoire car j'ai 1 Go disponible.
Cependant, lorsque j'essaie de lire le fichier, Python semble allouer beaucoup plus de mémoire que ce dont le fichier a besoin. Donc, même avec 1 Go de RAM, je ne parviens pas à lire le fichier de 500 Mo dans la mémoire. Mon code Python pour lire le fichier et imprimer des informations sur la consommation de mémoire est:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
infile=open("links.csv", "r")
edges=[]
count=0
#count the total number of lines in the file
for line in infile:
count=count+1
total=count
print "Total number of lines: ",total
infile.seek(0)
count=0
for line in infile:
Edge=Tuple(map(int,line.strip().split(",")))
edges.append(Edge)
count=count+1
# for every million lines print memory consumption
if count%1000000==0:
print "Position: ", Edge
print "Read ",float(count)/float(total)*100,"%."
mem=sys.getsizeof(edges)
for Edge in edges:
mem=mem+sys.getsizeof(Edge)
for node in Edge:
mem=mem+sys.getsizeof(node)
print "Memory (Bytes): ", mem
La sortie que j'ai eu était:
Total number of lines: 30609720
Position: (9745, 2994)
Read 3.26693612356 %.
Memory (Bytes): 64348736
Position: (38857, 103574)
Read 6.53387224712 %.
Memory (Bytes): 128816320
Position: (83609, 63498)
Read 9.80080837067 %.
Memory (Bytes): 192553000
Position: (139692, 1078610)
Read 13.0677444942 %.
Memory (Bytes): 257873392
Position: (205067, 153705)
Read 16.3346806178 %.
Memory (Bytes): 320107588
Position: (283371, 253064)
Read 19.6016167413 %.
Memory (Bytes): 385448716
Position: (354601, 377328)
Read 22.8685528649 %.
Memory (Bytes): 448629828
Position: (441109, 3024112)
Read 26.1354889885 %.
Memory (Bytes): 512208580
Déjà après avoir lu seulement 25% du fichier de 500 Mo, Python consomme 500 Mo. Il semble donc que stocker le contenu du fichier sous la forme d’une liste de tuples d’entités n’est pas très efficace en termes de mémoire. Mémoire?
Il existe une recette pour trier des fichiers plus volumineux que RAM sur cette page , bien que vous deviez l'adapter à votre cas impliquant des données au format CSV. Vous y trouverez également des liens vers des ressources supplémentaires.
Edit: True, le fichier sur le disque n’est pas "plus grand que la RAM", mais la représentation en mémoire peut facilement devenir beaucoup plus grande que disponible RAM. D'une part, votre propre programme ne reçoit pas l'intégralité de 1 Go (surcharge du système d'exploitation, etc.). D'autre part, même si vous stockiez cela sous la forme la plus compacte pour Python pur (deux listes d'entiers, en supposant une machine 32 bits, etc.), vous utiliseriez 934 Mo pour ces 30 millions de paires d'entiers.
En utilisant numpy, vous pouvez également faire le travail, en utilisant seulement environ 250 Mo. Ce n'est pas particulièrement rapide de charger de cette façon, car vous devez compter les lignes et pré-allouer le tableau, mais il peut s'agir du tri le plus rapide, vu qu'il est en mémoire:
import time
import numpy as np
import csv
start = time.time()
def elapsed():
return time.time() - start
# count data rows, to preallocate array
f = open('links.csv', 'rb')
def count(f):
while 1:
block = f.read(65536)
if not block:
break
yield block.count(',')
linecount = sum(count(f))
print '\n%.3fs: file has %s rows' % (elapsed(), linecount)
# pre-allocate array and load data into array
m = np.zeros(linecount, dtype=[('a', np.uint32), ('b', np.uint32)])
f.seek(0)
f = csv.reader(open('links.csv', 'rb'))
for i, row in enumerate(f):
m[i] = int(row[0]), int(row[1])
print '%.3fs: loaded' % elapsed()
# sort in-place
m.sort(order='b')
print '%.3fs: sorted' % elapsed()
Sortie sur ma machine avec un exemple de fichier similaire à ce que vous avez montré:
6.139s: file has 33253213 lines
238.130s: read into memory
517.669s: sorted
La valeur par défaut dans numpy est Quicksort . La routine ndarray.sort () (qui trie sur place) peut également prendre l'argument clé kind="mergesort"
ou kind="heapsort"
, mais il semble qu'aucune de celles-ci ne soit capable de trier sur un Record Array que j'ai utilisé comme seul moyen Je pouvais voir trier les colonnes ensemble par opposition à la valeur par défaut qui les trierait indépendamment (foirer totalement vos données).
Tous les objets python ont une surcharge de mémoire en plus des données qu’ils sont en train de stocker. Selon getsizeof de mon système Ubuntu 32 bits, un tuple représente 32 octets et un int prend 12 octets. Ainsi, chaque ligne de votre fichier utilise un pointeur de 56 octets + un pointeur de 4 octets dans la liste - je présume que ce sera beaucoup plus pour un système 64 bits. Cela correspond aux chiffres que vous avez donnés et signifie que vos 30 millions de lignes prendront 1,8 Go.
Je suggère qu'au lieu d'utiliser python, vous utilisiez l'utilitaire de tri Unix. Je ne suis pas un Mac-Head, mais je présume que les options de tri d'OS X sont identiques à celles de la version Linux. Cela devrait donc fonctionner:
sort -n -t, -k2 links.csv
-n signifie trier numériquement
-t, signifie utiliser une virgule comme séparateur de champ
-k2 signifie trier sur le deuxième champ
Cela va trier le fichier et écrire le résultat sur stdout. Vous pouvez le rediriger vers un autre fichier ou le diriger vers votre programme python pour effectuer un traitement ultérieur.
edit: Si vous ne souhaitez pas trier le fichier avant d'exécuter votre script python, vous pouvez utiliser le module de sous-processus pour créer un canal vers l'utilitaire de tri Shell, puis lire les résultats triés à partir de la sortie du canal. .
Comme ce ne sont que des nombres, les charger dans un tableau Nx2 enlèverait une surcharge. Utilisez NumPy pour les tableaux multidimensionnels. Alternativement, vous pouvez utiliser deux python arrays normaux pour représenter chaque colonne.
Le moyen le moins coûteux de stocker les lignes d’entrée en mémoire consiste à utiliser des éléments array.array ('i') - en supposant que chaque nombre tiendra dans un entier signé de 32 bits. Le coût de la mémoire sera de 8N octets, N étant le nombre de lignes.
Voici comment faire le tri et écrire le fichier de sortie dans l'ordre trié:
from array import array
import csv
a = array('i')
b = array('i')
for anum, bnum in csv.reader(open('input.csv', 'rb')):
a.append(int(anum))
b.append(int(bnum))
wtr = csv.writer(open('output.csv', 'wb'))
for i in sorted(xrange(len(a)), key=lambda x: b[x]):
wtr.writerow([a[i], b[i]])
Malheureusement, sorted()
renvoie une liste, pas un itérateur, et cette liste sera plutôt longue: 4N octets pour les pointeurs et 12N octets pour les objets int, c’est-à-dire 16N octets pour la sortie sorted()
. Remarque: ceci est basé sur CPython 2.X sur une machine 32 bits; la situation empire pour les machines 3.X et 64 bits. Tout cela fait 24N octets. Vous avez 31 millions de lignes, vous avez donc besoin de 31 * 24 = 744 Mo ... semble devoir fonctionner; Notez que ce calcul ne permet aucune mémoire allouée par le tri, mais vous disposez d'une marge de sécurité raisonnable.
De côté: Quel est le coût d'un gigaoctet supplémentaire ou de 3 de mémoire exprimé en heures à votre taux de salaire?
Vous voudrez peut-être regarder mmap:
http://docs.python.org/library/mmap.html
Cela vous permettra de traiter le fichier comme un grand tableau/chaîne et obligera le système d'exploitation à gérer le brassage des données dans et hors de la mémoire pour le laisser entrer.
Vous pouvez donc lire le fichier csv, ligne par ligne, puis écrire les résultats dans un fichier mmap'd (dans un format binaire approprié), puis travailler sur le fichier mmap'd. Comme le fichier mmap'd n’est que temporaire, vous pouvez bien sûr créer un fichier tmp à cet effet.
Voici quelques exemples de code utilisant mmap avec un fichier temporaire pour lire les données csv et les stocker sous forme de paires d'entiers:
import sys
import mmap
import array
from tempfile import TemporaryFile
def write_int(buffer, i):
# convert i to 4 bytes and write into buffer
buffer.write(array.array('i', [i]).tostring())
def read_int(buffer, pos):
# get the 4 bytes at pos and convert to integer
offset = 4*pos
return array.array('i', buffer[offset:offset+4])[0]
def get_Edge(edges, lineno):
pos = lineno*2
i, j = read_int(edges, pos), read_int(edges, pos+1)
return i, j
infile=open("links.csv", "r")
count=0
#count the total number of lines in the file
for line in infile:
count=count+1
total=count
print "Total number of lines: ",total
infile.seek(0)
# make mmap'd file that's long enough to contain all data
# assuming two integers (4 bytes) per line
tmp = TemporaryFile()
file_len = 2*4*count
# increase tmp file size
tmp.seek(file_len-1)
tmp.write(' ')
tmp.seek(0)
edges = mmap.mmap(tmp.fileno(), file_len)
for line in infile:
i, j=Tuple(map(int,line.strip().split(",")))
write_int(edges, i)
write_int(edges, j)
# now confirm we can read the ints back out ok
for i in xrange(count):
print get_Edge(edges, i)
C'est un peu dur cependant. Vraiment, vous voudrez probablement terminer tout cela avec une classe Nice, pour pouvoir accéder à votre Edge de manière à ce qu’ils se comportent comme une liste (avec indexation, len, etc.). Espérons que cela vous donnerait un point de départ.
J'ai créé un module pour ce cas d'utilisation en utilisant type de fusion externe : https://bitbucket.org/richardpenman/csvsort
>>> from csvsort import csvsort
>>> csvsort('links.csv', columns=[1], has_header=False)