web-dev-qa-db-fra.com

Limite supérieure de mémoire?

Y a-t-il une limite de mémoire pour python? J'utilise un script python pour calculer les valeurs moyennes à partir d'un fichier d'une taille minimale de 150 Mo.

Selon la taille du fichier, je rencontre parfois un MemoryError.

Peut-on affecter plus de mémoire au python pour que je ne rencontre pas l'erreur?


EDIT: Code maintenant ci-dessous

REMARQUE: la taille des fichiers peut varier considérablement (jusqu'à 20 Go), la taille minimale d'un fichier est de 150 Mo

file_A1_B1 = open("A1_B1_100000.txt", "r")
file_A2_B2 = open("A2_B2_100000.txt", "r")
file_A1_B2 = open("A1_B2_100000.txt", "r")
file_A2_B1 = open("A2_B1_100000.txt", "r")
file_write = open ("average_generations.txt", "w")
mutation_average = open("mutation_average", "w")

files = [file_A2_B2,file_A2_B2,file_A1_B2,file_A2_B1]

for u in files:
    line = u.readlines()
    list_of_lines = []
    for i in line:
        values = i.split('\t')
        list_of_lines.append(values)

    count = 0
    for j in list_of_lines:
        count +=1

    for k in range(0,count):
        list_of_lines[k].remove('\n')

    length = len(list_of_lines[0])
    print_counter = 4

    for o in range(0,length):
        total = 0
        for p in range(0,count):
            number = float(list_of_lines[p][o])
            total = total + number
        average = total/count
        print average
        if print_counter == 4:
            file_write.write(str(average)+'\n')
            print_counter = 0
        print_counter +=1
file_write.write('\n')
25
Harpal

(Ceci est ma troisième réponse parce que j'ai mal compris ce que faisait votre code dans mon original, puis j'ai fait une petite mais cruciale erreur dans ma seconde — j'espère que trois est un charme.

Modifications: Comme cela semble être une réponse populaire, j'ai apporté quelques modifications pour améliorer sa mise en œuvre au fil des ans, la plupart n'étant pas trop importantes. C'est le cas si les gens l'utilisent comme modèle, cela fournira une base encore meilleure.

Comme d'autres l'ont souligné, votre problème MemoryError est très probablement dû au fait que vous essayez de lire l'intégralité du contenu des fichiers volumineux en mémoire, puis, en plus de cela, de doubler efficacement la quantité de mémoire nécessaire en créant un liste des listes des valeurs de chaîne de chaque ligne.

Les limites de mémoire de Python sont déterminées par la quantité de RAM physique et d'espace disque de mémoire virtuelle dont votre ordinateur et votre système d'exploitation disposent. Même si vous ne l'utilisez pas entièrement et que votre programme "fonctionne", son utilisation peut s'avérer peu pratique car elle prend trop de temps.

Quoi qu'il en soit, la façon la plus évidente d'éviter cela est de traiter chaque fichier une seule ligne à la fois, ce qui signifie que vous devez effectuer le traitement de manière incrémentielle.

Pour ce faire, une liste des totaux cumulés pour chacun des champs est conservée. Lorsque cela est terminé, la valeur moyenne de chaque champ peut être calculée en divisant la valeur totale correspondante par le nombre de lignes totales lues. Une fois cela fait, ces moyennes peuvent être imprimées et certaines écrites dans l'un des fichiers de sortie. J'ai également fait un effort conscient pour utiliser des noms de variables très descriptifs pour essayer de le rendre compréhensible.

try:
    from itertools import izip_longest
except ImportError:    # Python 3
    from itertools import Zip_longest as izip_longest

GROUP_SIZE = 4
input_file_names = ["A1_B1_100000.txt", "A2_B2_100000.txt", "A1_B2_100000.txt",
                    "A2_B1_100000.txt"]
file_write = open("average_generations.txt", 'w')
mutation_average = open("mutation_average", 'w')  # left in, but nothing written

for file_name in input_file_names:
    with open(file_name, 'r') as input_file:
        print('processing file: {}'.format(file_name))

        totals = []
        for count, fields in enumerate((line.split('\t') for line in input_file), 1):
            totals = [sum(values) for values in
                        izip_longest(totals, map(float, fields), fillvalue=0)]
        averages = [total/count for total in totals]

        for print_counter, average in enumerate(averages):
            print('  {:9.4f}'.format(average))
            if print_counter % GROUP_SIZE == 0:
                file_write.write(str(average)+'\n')

file_write.write('\n')
file_write.close()
mutation_average.close()
30
martineau

Vous lisez l'intégralité du fichier en mémoire (line = u.readlines()), ce qui échouera bien sûr si le fichier est trop volumineux (et vous dites que certains font jusqu'à 20 Go), c'est donc votre problème.

Mieux itérer sur chaque ligne:

for current_line in u:
    do_something_with(current_line)

est l'approche recommandée.

Plus loin dans votre script, vous faites des choses très étranges, comme compter d'abord tous les éléments d'une liste, puis construire une boucle for sur la plage de ce nombre. Pourquoi ne pas parcourir directement la liste? Quel est le but de votre script? J'ai l'impression que cela pourrait être fait beaucoup plus facilement.

C'est l'un des avantages des langages de haut niveau comme Python (par opposition à C où vous devez effectuer ces tâches de ménage vous-même)): Allow Python pour gérer l'itération pour vous et ne collecter en mémoire que ce dont vous avez réellement besoin à tout moment.

De plus, comme il semble que vous traitez des fichiers TSV (valeurs séparées par des tabulateurs), vous devriez jeter un œil au module csv qui gérera tout le fractionnement, la suppression de \ns etc. pour vous.

18
Tim Pietzcker

Python peut utiliser toute la mémoire disponible pour son environnement. Mon simple "test de mémoire" se bloque sur ActiveState Python 2.6 après avoir utilisé environ

1959167 [MiB]

Sur jython 2.5, il plante plus tôt:

 239000 [MiB]

je peux probablement configurer Jython pour utiliser plus de mémoire (il utilise les limites de JVM)

Application de test:

import sys

sl = []
i = 0
# some magic 1024 - overhead of string object
fill_size = 1024
if sys.version.startswith('2.7'):
    fill_size = 1003
if sys.version.startswith('3'):
    fill_size = 497
print(fill_size)
MiB = 0
while True:
    s = str(i).zfill(fill_size)
    sl.append(s)
    if i == 0:
        try:
            sys.stderr.write('size of one string %d\n' % (sys.getsizeof(s)))
        except AttributeError:
            pass
    i += 1
    if i % 1024 == 0:
        MiB += 1
        if MiB % 25 == 0:
            sys.stderr.write('%d [MiB]\n' % (MiB))

Dans votre application, vous lisez tout le fichier à la fois. Pour ces gros fichiers, vous devez lire la ligne par ligne.

17
Michał Niklas

Non, il n'y a pas de limite spécifique à Python sur l'utilisation de la mémoire d'une application Python. Je travaille régulièrement avec des applications Python qui peuvent utiliser plusieurs gigaoctets de mémoire. La plupart probablement, votre script utilise en fait plus de mémoire que disponible sur la machine que vous utilisez.

Dans ce cas, la solution consiste à réécrire le script pour être plus efficace en mémoire, ou à ajouter plus de mémoire physique si le script est déjà optimisé pour minimiser l'utilisation de la mémoire.

Modifier:

Votre script lit tout le contenu de vos fichiers en mémoire à la fois (line = u.readlines()). Étant donné que vous traitez des fichiers jusqu'à 20 Go, vous obtiendrez des erreurs de mémoire avec cette approche, sauf si vous avez d'énormes quantités de mémoire dans votre machine.

Une meilleure approche serait de lire les fichiers une ligne à la fois:

for u in files:
     for line in u: # This will iterate over each line in the file
         # Read values from the line, do necessary calculations
8
Pär Wieslander

Non seulement vous lisez l'intégralité de chaque fichier en mémoire, mais vous répliquez aussi laborieusement les informations dans un tableau appelé list_of_lines.

Vous avez un problème secondaire: vos choix de noms de variables obscurcissent sévèrement ce que vous faites.

Voici votre script réécrit avec le caper readlines () supprimé et avec des noms significatifs:

file_A1_B1 = open("A1_B1_100000.txt", "r")
file_A2_B2 = open("A2_B2_100000.txt", "r")
file_A1_B2 = open("A1_B2_100000.txt", "r")
file_A2_B1 = open("A2_B1_100000.txt", "r")
file_write = open ("average_generations.txt", "w")
mutation_average = open("mutation_average", "w") # not used
files = [file_A2_B2,file_A2_B2,file_A1_B2,file_A2_B1]
for afile in files:
    table = []
    for aline in afile:
        values = aline.split('\t')
        values.remove('\n') # why?
        table.append(values)
    row_count = len(table)
    row0length = len(table[0])
    print_counter = 4
    for column_index in range(row0length):
        column_total = 0
        for row_index in range(row_count):
            number = float(table[row_index][column_index])
            column_total = column_total + number
        column_average = column_total/row_count
        print column_average
        if print_counter == 4:
            file_write.write(str(column_average)+'\n')
            print_counter = 0
        print_counter +=1
file_write.write('\n')

Il devient rapidement évident que (1) vous calculez les moyennes des colonnes (2) l'obscurcissement a conduit certains autres à penser que vous calculiez les moyennes des lignes.

Lorsque vous calculez des moyennes de colonnes, aucune sortie n'est requise jusqu'à la fin de chaque fichier et la quantité de mémoire supplémentaire réellement requise est proportionnelle au nombre de colonnes.

Voici une version révisée du code de la boucle externe:

for afile in files:
    for row_count, aline in enumerate(afile, start=1):
        values = aline.split('\t')
        values.remove('\n') # why?
        fvalues = map(float, values)
        if row_count == 1:
            row0length = len(fvalues)
            column_index_range = range(row0length)
            column_totals = fvalues
        else:
            assert len(fvalues) == row0length
            for column_index in column_index_range:
                column_totals[column_index] += fvalues[column_index]
    print_counter = 4
    for column_index in column_index_range:
        column_average = column_totals[column_index] / row_count
        print column_average
        if print_counter == 4:
            file_write.write(str(column_average)+'\n')
            print_counter = 0
        print_counter +=1
5
John Machin