J'ai écrit le programme (ci-dessous) pour:
pandas dataframe
groupby
en utilisant une valeur de colonne spécifique pour diviser les données et les stocker sous forme de liste de trames de données.multiprocess Pool.map()
pour traiter chaque trame de données en parallèle.Tout va bien, le programme fonctionne bien sur mon petit jeu de données de test. Mais, lorsque je canalise mes grandes données (environ 14 Go), la consommation de mémoire augmente de façon exponentielle, puis fige l'ordinateur ou se fait tuer (dans le cluster HPC).
J'ai ajouté des codes pour effacer la mémoire dès que les données/variables ne sont pas utiles. Je ferme également la piscine dès qu'elle est terminée. Toujours avec une entrée de 14 Go, je ne m'attendais qu'à une charge de mémoire de 2 * 14 Go, mais il semble que beaucoup de choses se passent. J'ai également essayé de modifier en utilisant chunkSize and maxTaskPerChild, etc
Mais je ne vois aucune différence d'optimisation dans les deux tests par rapport au gros fichier.
Je pense que des améliorations de ce code sont/sont nécessaires à cette position de code, lorsque je démarre multiprocessing
.
p = Pool(3) # number of pool to run at once; default at 1 result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
mais, je publie tout le code.
Exemple de test: J'ai créé un fichier de test ("genome_matrix_final-chr1234-1mb.txt") jusqu'à 250 Mo et j'ai exécuté le programme. Lorsque je vérifie le moniteur système, je constate que la consommation de mémoire a augmenté d'environ 6 Go. Je ne sais pas trop pourquoi tant d'espace mémoire est pris par un fichier de 250 Mo plus quelques sorties. J'ai partagé ce fichier via une boîte de dépôt si cela aide à voir le vrai problème. https://www.dropbox.com/sh/coihujii38t5prd/AABDXv8ACGIYczeMtzKBo0eea?dl=
Quelqu'un peut-il suggérer: Comment puis-je me débarrasser du problème?
Mon python:
#!/home/bin/python3
import pandas as pd
import collections
from multiprocessing import Pool
import io
import time
import resource
print()
print('Checking required modules')
print()
''' change this input file name and/or path as need be '''
genome_matrix_file = "genome_matrix_final-chr1n2-2mb.txt" # test file 01
genome_matrix_file = "genome_matrix_final-chr1234-1mb.txt" # test file 02
#genome_matrix_file = "genome_matrix_final.txt" # large file
def main():
with open("genome_matrix_header.txt") as header:
header = header.read().rstrip('\n').split('\t')
print()
time01 = time.time()
print('starting time: ', time01)
'''load the genome matrix file onto pandas as dataframe.
This makes is more easy for multiprocessing'''
gen_matrix_df = pd.read_csv(genome_matrix_file, sep='\t', names=header)
# now, group the dataframe by chromosome/contig - so it can be multiprocessed
gen_matrix_df = gen_matrix_df.groupby('CHROM')
# store the splitted dataframes as list of key, values(pandas dataframe) pairs
# this list of dataframe will be used while multiprocessing
gen_matrix_df_list = collections.OrderedDict()
for chr_, data in gen_matrix_df:
gen_matrix_df_list[chr_] = data
# clear memory
del gen_matrix_df
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
del gen_matrix_df_list # clear memory
p.close()
p.join()
# concat the results from pool.map() and write it to a file
result_merged = pd.concat(result)
del result # clear memory
pd.DataFrame.to_csv(result_merged, "matrix_to_haplotype-chr1n2.txt", sep='\t', header=True, index=False)
print()
print('completed all process in "%s" sec. ' % (time.time() - time01))
print('Global maximum memory usage: %.2f (mb)' % current_mem_usage())
print()
'''function to convert the dataframe from genome matrix to desired output '''
def matrix_to_vcf(matrix_df):
print()
time02 = time.time()
# index position of the samples in genome matrix file
sample_idx = [{'10a': 33, '10b': 18}, {'13a': 3, '13b': 19},
{'14a': 20, '14b': 4}, {'16a': 5, '16b': 21},
{'17a': 6, '17b': 22}, {'23a': 7, '23b': 23},
{'24a': 8, '24b': 24}, {'25a': 25, '25b': 9},
{'26a': 10, '26b': 26}, {'34a': 11, '34b': 27},
{'35a': 12, '35b': 28}, {'37a': 13, '37b': 29},
{'38a': 14, '38b': 30}, {'3a': 31, '3b': 15},
{'8a': 32, '8b': 17}]
# sample index stored as ordered dictionary
sample_idx_ord_list = []
for ids in sample_idx:
ids = collections.OrderedDict(sorted(ids.items()))
sample_idx_ord_list.append(ids)
# for haplotype file
header = ['contig', 'pos', 'ref', 'alt']
# adding some suffixes "PI" to available sample names
for item in sample_idx_ord_list:
ks_update = ''
for ks in item.keys():
ks_update += ks
header.append(ks_update+'_PI')
header.append(ks_update+'_PG_al')
#final variable store the haplotype data
# write the header lines first
haplotype_output = '\t'.join(header) + '\n'
# to store the value of parsed the line and update the "PI", "PG" value for each sample
updated_line = ''
# read the piped in data back to text like file
matrix_df = pd.DataFrame.to_csv(matrix_df, sep='\t', index=False)
matrix_df = matrix_df.rstrip('\n').split('\n')
for line in matrix_df:
if line.startswith('CHROM'):
continue
line_split = line.split('\t')
chr_ = line_split[0]
ref = line_split[2]
alt = list(set(line_split[3:]))
# remove the alleles "N" missing and "ref" from the alt-alleles
alt_up = list(filter(lambda x: x!='N' and x!=ref, alt))
# if no alt alleles are found, just continue
# - i.e : don't write that line in output file
if len(alt_up) == 0:
continue
#print('\nMining data for chromosome/contig "%s" ' %(chr_ ))
#so, we have data for CHR, POS, REF, ALT so far
# now, we mine phased genotype for each sample pair (as "PG_al", and also add "PI" tag)
sample_data_for_vcf = []
for ids in sample_idx_ord_list:
sample_data = []
for key, val in ids.items():
sample_value = line_split[val]
sample_data.append(sample_value)
# now, update the phased state for each sample
# also replacing the missing allele i.e "N" and "-" with ref-allele
sample_data = ('|'.join(sample_data)).replace('N', ref).replace('-', ref)
sample_data_for_vcf.append(str(chr_))
sample_data_for_vcf.append(sample_data)
# add data for all the samples in that line, append it with former columns (chrom, pos ..) ..
# and .. write it to final haplotype file
sample_data_for_vcf = '\t'.join(sample_data_for_vcf)
updated_line = '\t'.join(line_split[0:3]) + '\t' + ','.join(alt_up) + \
'\t' + sample_data_for_vcf + '\n'
haplotype_output += updated_line
del matrix_df # clear memory
print('completed haplotype preparation for chromosome/contig "%s" '
'in "%s" sec. ' %(chr_, time.time()-time02))
print('\tWorker maximum memory usage: %.2f (mb)' %(current_mem_usage()))
# return the data back to the pool
return pd.read_csv(io.StringIO(haplotype_output), sep='\t')
''' to monitor memory '''
def current_mem_usage():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.
if __name__ == '__main__':
main()
Mise à jour pour les chasseurs de primes:
J'ai réalisé le multitraitement en utilisant Pool.map()
mais le code cause une charge mémoire importante (fichier de test d'entrée ~ 300 Mo, mais la charge mémoire est d'environ 6 Go). Je m'attendais seulement à une charge de mémoire de 3 * 300 Mo au maximum.
Dans Python (dans ce qui suit, j'utilise une version 64 bits de Python 3.6.5) tout est un objet. Cela a sa surcharge et avec getsizeof
nous pouvons voir exactement la taille d'un objet en octets:
>>> import sys
>>> sys.getsizeof(42)
28
>>> sys.getsizeof('T')
50
multiprocessing.get_start_method()
) pour créer un processus enfant, la mémoire physique du parent n'est pas copiée et la technique copie sur écriture est utilisée.
- Le processus A dispose de 50 Ko de mémoire non partagée
- Le processus B dispose de 300 Ko de mémoire non partagée
- Le processus A et le processus B ont tous les deux 100 Ko de la même région de mémoire partagée
Étant donné que le PSS est défini comme la somme de la mémoire non partagée d'un processus et de la proportion de mémoire partagée avec d'autres processus, le PSS pour ces deux processus est le suivant:
- PSS du processus A = 50 KiB + (100 KiB/2) = 100 KiB
- PSS du processus B = 300 KiB + (100 KiB/2) = 350 KiB
Ne regardons pas votre DataFrame
seul. memory_profiler
nous aidera.
justpd.py
#!/usr/bin/env python3
import pandas as pd
from memory_profiler import profile
@profile
def main():
with open('genome_matrix_header.txt') as header:
header = header.read().rstrip('\n').split('\t')
gen_matrix_df = pd.read_csv(
'genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
gen_matrix_df.info()
gen_matrix_df.info(memory_usage='deep')
if __name__ == '__main__':
main()
Utilisons maintenant le profileur:
mprof run justpd.py
mprof plot
Nous pouvons voir l'intrigue:
et trace ligne par ligne:
Line # Mem usage Increment Line Contents
================================================
6 54.3 MiB 54.3 MiB @profile
7 def main():
8 54.3 MiB 0.0 MiB with open('genome_matrix_header.txt') as header:
9 54.3 MiB 0.0 MiB header = header.read().rstrip('\n').split('\t')
10
11 2072.0 MiB 2017.7 MiB gen_matrix_df = pd.read_csv('genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
12
13 2072.0 MiB 0.0 MiB gen_matrix_df.info()
14 2072.0 MiB 0.0 MiB gen_matrix_df.info(memory_usage='deep')
Nous pouvons voir que la trame de données prend ~ 2 GiB avec un pic à ~ 3 GiB pendant sa construction. Ce qui est plus intéressant, c'est la sortie de info
.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4000000 entries, 0 to 3999999
Data columns (total 34 columns):
...
dtypes: int64(2), object(32)
memory usage: 1.0+ GB
Mais info(memory_usage='deep')
("deep" signifie une introspection approfondie des données en interrogeant object
dtype
s, voir ci-dessous) donne:
memory usage: 7.9 GB
Hein?! En regardant en dehors du processus, nous pouvons nous assurer que les chiffres de memory_profiler
Sont corrects. sys.getsizeof
Affiche également la même valeur pour le cadre (probablement à cause de __sizeof__
Personnalisé) et il en sera de même pour les autres outils qui l'utilisent pour estimer la gc.get_objects()
allouée, par exemple pympler
.
# added after read_csv
from pympler import tracker
tr = tracker.SummaryTracker()
tr.print_diff()
Donne:
types | # objects | total size
================================================== | =========== | ============
<class 'pandas.core.series.Series | 34 | 7.93 GB
<class 'list | 7839 | 732.38 KB
<class 'str | 7741 | 550.10 KB
<class 'int | 1810 | 49.66 KB
<class 'dict | 38 | 7.43 KB
<class 'pandas.core.internals.SingleBlockManager | 34 | 3.98 KB
<class 'numpy.ndarray | 34 | 3.19 KB
Alors d'où viennent ces 7,93 GiB? Essayons d'expliquer cela. Nous avons 4M lignes et 34 colonnes, ce qui nous donne 134M valeurs. Ce sont soit int64
Ou object
(qui est un pointeur 64 bits; voir en utilisant pandas avec de grandes données pour une explication détaillée). Ainsi, nous avons 134 * 10 ** 6 * 8 / 2 ** 20
~ 1022 Mio uniquement pour les valeurs de la trame de données. Qu'en est-il des ~ 6,93 Gio restants?
Pour comprendre le comportement, il est nécessaire de savoir que Python fait l'internement de chaînes. Il y a deux bons articles ( n , deux ) sur l'internement de chaînes in Python 2. Outre le changement Unicode dans Python 3 et PEP 39 in Python 3.3 les structures C ont changé, mais l'idée est la même. Fondamentalement, chaque chaîne courte qui ressemble à un identifiant sera mise en cache par Python dans un dictionnaire interne et les références pointeront vers le même Python objets. Dans un autre Word, nous pouvons dire qu'il se comporte comme un singleton. Les articles que j'ai mentionnés ci-dessus expliquent le profil de mémoire significatif et les améliorations de performances qu'il donne. Nous pouvons vérifier si une chaîne est internée en utilisant - interned
champ de PyASCIIObject
:
import ctypes
class PyASCIIObject(ctypes.Structure):
_fields_ = [
('ob_refcnt', ctypes.c_size_t),
('ob_type', ctypes.py_object),
('length', ctypes.c_ssize_t),
('hash', ctypes.c_int64),
('state', ctypes.c_int32),
('wstr', ctypes.c_wchar_p)
]
Ensuite:
>>> a = 'name'
>>> b = '!@#$'
>>> a_struct = PyASCIIObject.from_address(id(a))
>>> a_struct.state & 0b11
1
>>> b_struct = PyASCIIObject.from_address(id(b))
>>> b_struct.state & 0b11
0
Avec deux chaînes, nous pouvons également faire une comparaison d'identité (abordée dans la comparaison de mémoire dans le cas de CPython).
>>> a = 'foo'
>>> b = 'foo'
>>> a is b
True
>> gen_matrix_df.REF[0] is gen_matrix_df.REF[6]
True
De ce fait, en ce qui concerne object
dtype
, la trame de données alloue au plus 20 chaînes (une par acides aminés). Cependant, il convient de noter que Pandas recommande types catégoriels pour les énumérations.
Ainsi, nous pouvons expliquer l'estimation naïve de 7,93 GiB comme:
>>> rows = 4 * 10 ** 6
>>> int_cols = 2
>>> str_cols = 32
>>> int_size = 8
>>> str_size = 58
>>> ptr_size = 8
>>> (int_cols * int_size + str_cols * (str_size + ptr_size)) * rows / 2 ** 30
7.927417755126953
Notez que str_size
Est de 58 octets, pas 50 comme nous l'avons vu ci-dessus pour un littéral à 1 caractère. C'est parce que PEP 393 définit des chaînes compactes et non compactes. Vous pouvez le vérifier avec sys.getsizeof(gen_matrix_df.REF[0])
.
La consommation réelle de mémoire doit être ~ 1 GiB comme indiqué par gen_matrix_df.info()
, c'est deux fois plus. Nous pouvons supposer que cela a quelque chose à voir avec l'allocation (pré) de mémoire effectuée par Pandas ou NumPy. L'expérience suivante montre que ce n'est pas sans raison (plusieurs exécutions montrent l'image de sauvegarde):
Line # Mem usage Increment Line Contents
================================================
8 53.1 MiB 53.1 MiB @profile
9 def main():
10 53.1 MiB 0.0 MiB with open("genome_matrix_header.txt") as header:
11 53.1 MiB 0.0 MiB header = header.read().rstrip('\n').split('\t')
12
13 2070.9 MiB 2017.8 MiB gen_matrix_df = pd.read_csv('genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
14 2071.2 MiB 0.4 MiB gen_matrix_df = gen_matrix_df.drop(columns=[gen_matrix_df.keys()[0]])
15 2071.2 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[gen_matrix_df.keys()[0]])
16 2040.7 MiB -30.5 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
...
23 1827.1 MiB -30.5 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
24 1094.7 MiB -732.4 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
25 1765.9 MiB 671.3 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
26 1094.7 MiB -671.3 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
27 1704.8 MiB 610.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
28 1094.7 MiB -610.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
29 1643.9 MiB 549.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
30 1094.7 MiB -549.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
31 1582.8 MiB 488.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
32 1094.7 MiB -488.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
33 1521.9 MiB 427.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
34 1094.7 MiB -427.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
35 1460.8 MiB 366.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
36 1094.7 MiB -366.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
37 1094.7 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
...
47 1094.7 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
Je veux terminer cette section par une citation de nouvel article sur les problèmes de conception et les futurs Pandas2 par l'auteur original de Pandas.
règle de base des pandas: avoir 5 à 10 fois plus RAM que la taille de votre ensemble de données
Venons-en enfin au pool et voyons si nous pouvons utiliser la copie sur écriture. Nous utiliserons smemstat
(disponible depuis un référentiel Ubuntu) pour estimer le partage de mémoire du groupe de processus et glances
pour écrire à l'échelle du système mémoire libre. Les deux peuvent écrire en JSON.
Nous exécuterons le script original avec Pool(2)
. Nous aurons besoin de 3 fenêtres de terminal.
smemstat -l -m -p "python3.6 script.py" -o smemstat.json 1
glances -t 1 --export-json glances.json
mprof run -M script.py
Alors mprof plot
Produit:
Le graphique de somme (mprof run --nopython --include-children ./script.py
) Ressemble à:
Notez que deux graphiques ci-dessus montrent RSS. L'hypothèse est qu'en raison de la copie sur écriture, elle ne reflète pas l'utilisation réelle de la mémoire. Nous avons maintenant deux fichiers JSON de smemstat
et glances
. Je vais le script suivant pour convertir les fichiers JSON en CSV.
#!/usr/bin/env python3
import csv
import sys
import json
def smemstat():
with open('smemstat.json') as f:
smem = json.load(f)
rows = []
fieldnames = set()
for s in smem['smemstat']['periodic-samples']:
row = {}
for ps in s['smem-per-process']:
if 'script.py' in ps['command']:
for k in ('uss', 'pss', 'rss'):
row['{}-{}'.format(ps['pid'], k)] = ps[k] // 2 ** 20
# smemstat produces empty samples, backfill from previous
if rows:
for k, v in rows[-1].items():
row.setdefault(k, v)
rows.append(row)
fieldnames.update(row.keys())
with open('smemstat.csv', 'w') as out:
dw = csv.DictWriter(out, fieldnames=sorted(fieldnames))
dw.writeheader()
list(map(dw.writerow, rows))
def glances():
rows = []
fieldnames = ['available', 'used', 'cached', 'mem_careful', 'percent',
'free', 'mem_critical', 'inactive', 'shared', 'history_size',
'mem_warning', 'total', 'active', 'buffers']
with open('glances.csv', 'w') as out:
dw = csv.DictWriter(out, fieldnames=fieldnames)
dw.writeheader()
with open('glances.json') as f:
for l in f:
d = json.loads(l)
dw.writerow(d['mem'])
if __name__ == '__main__':
globals()[sys.argv[1]]()
Examinons d'abord la mémoire free
.
La différence entre le premier et le minimum est de ~ 4,15 Gio. Et voici à quoi ressemblent les chiffres PSS:
Et la somme:
Ainsi, nous pouvons voir qu'en raison de la copie sur écriture, la consommation de mémoire réelle est d'environ 4,15 Gio. Mais nous sérialisons toujours les données pour les envoyer aux processus de travail via Pool.map
. Pouvons-nous également tirer parti de la copie sur écriture?
Pour utiliser la copie sur écriture, nous devons avoir la list(gen_matrix_df_list.values())
accessible globalement afin que le travailleur après fork puisse toujours la lire.
Modifions le code après del gen_matrix_df
Dans main
comme suit:
...
global global_gen_matrix_df_values
global_gen_matrix_df_values = list(gen_matrix_df_list.values())
del gen_matrix_df_list
p = Pool(2)
result = p.map(matrix_to_vcf, range(len(global_gen_matrix_df_values)))
...
del gen_matrix_df_list
Qui va plus tard.Et modifiez les premières lignes de matrix_to_vcf
Comme:
def matrix_to_vcf(i):
matrix_df = global_gen_matrix_df_values[i]
Maintenant, réexécutons-le. Mémoire libre:
Arbre de processus:
Et sa somme:
Ainsi, nous sommes au maximum de ~ 2,9 GiB d'utilisation réelle de la mémoire (le processus principal de pointe a lors de la construction du bloc de données) et la copie sur écriture a aidé!
En guise de remarque, il y a ce qu'on appelle la copie en lecture, le comportement du garbage collector du cycle de référence de Python, décrit dans Instagram Engineering (ce qui a conduit à gc.freeze
Dans issue31558 ). Mais gc.disable()
n'a pas d'impact dans ce cas particulier.
Une alternative au partage de données sans copie avec copie sur écriture peut être de le déléguer au noyau depuis le début en utilisant numpy.memmap
. Voici n exemple d'implémentation de Traitement de données hautes performances en Python parler. La partie délicate consiste alors à faire Pandas pour utiliser le tableau Numpy mmapé.
J'ai eu le même problème. J'avais besoin de traiter un énorme corpus de texte tout en conservant une base de connaissances de quelques DataFrames de millions de lignes chargées en mémoire. Je pense que ce problème est courant, je vais donc garder ma réponse orientée à des fins générales.
Une combinaison de paramètres a résolu le problème pour moi (1 & 3 & 5 seulement pourrait le faire pour vous):
Utilisez Pool.imap
(Ou imap_unordered
) Au lieu de Pool.map
. Cela parcourra les données paresseusement plutôt que de les charger toutes en mémoire avant de commencer le traitement.
Définissez une valeur sur le paramètre chunksize
. Cela rendra également imap
plus rapide.
Définissez une valeur sur le paramètre maxtasksperchild
.
Ajoutez la sortie au disque plutôt qu'en mémoire. Instantanément ou à tout moment lorsqu'il atteint une certaine taille.
Exécutez le code en différents lots. Vous pouvez utiliser itertools.islice si vous avez un itérateur. L'idée est de diviser votre list(gen_matrix_df_list.values())
en trois listes ou plus, puis vous ne passez que le premier tiers à map
ou imap
, puis le deuxième tiers dans une autre exécution, etc. Puisque vous avez une liste, vous pouvez simplement la découper dans la même ligne de code.
Lorsque vous utilisez multiprocessing.Pool
, Un certain nombre de processus enfants seront créés à l'aide de l'appel système fork()
. Chacun de ces processus démarre avec une copie exacte de la mémoire du processus parent à ce moment-là. Comme vous chargez le fichier csv avant de créer le Pool
de taille 3, chacun de ces 3 processus dans le pool aura inutilement une copie du bloc de données. (gen_matrix_df
Ainsi que gen_matrix_df_list
Existeront dans le processus actuel ainsi que dans chacun des 3 processus enfants, donc 4 copies de chacune de ces structures seront en mémoire)
Essayez de créer le Pool
avant de charger le fichier (au tout début en fait) Cela devrait réduire l'utilisation de la mémoire.
S'il est encore trop élevé, vous pouvez:
Dump gen_matrix_df_list dans un fichier, 1 élément par ligne, par exemple:
import os
import cPickle
with open('tempfile.txt', 'w') as f:
for item in gen_matrix_df_list.items():
cPickle.dump(item, f)
f.write(os.linesep)
Utilisez Pool.imap()
sur un itérateur sur les lignes que vous avez sauvegardées dans ce fichier, par exemple:
with open('tempfile.txt', 'r') as f:
p.imap(matrix_to_vcf, (cPickle.loads(line) for line in f))
(Notez que matrix_to_vcf
Prend un tuple (key, value)
Dans l'exemple ci-dessus, pas seulement une valeur)
J'espère que ça aide.
NB: je n'ai pas testé le code ci-dessus. Il ne s'agit que de démontrer l'idée.
RÉPONSE GÉNÉRALE SUR LA MÉMOIRE AVEC LE MULTI-TRAITEMENT
Vous avez demandé: "Qu'est-ce qui fait que tant de mémoire est allouée". La réponse repose sur deux parties.
D'abord, comme vous l'avez déjà remarqué, chaque multiprocessing
travailleur obtient sa propre copie des données ( entre guillemets d'ici ), vous devez donc découper de gros arguments. Ou pour les fichiers volumineux, lisez-les petit à petit, si possible.
Par défaut, les travailleurs du pool sont de vrais processus Python bifurqués à l'aide du module multiprocessing de la bibliothèque standard Python lorsque n_jobs! = 1. Les arguments passés en entrée à l'appel parallèle sont sérialisés et réalloués dans la mémoire de chaque processus de travail.
Cela peut être problématique pour les gros arguments car ils seront réaffectés n_jobs fois par les travailleurs.
Second, si vous essayez de récupérer de la mémoire, vous devez comprendre que python fonctionne différemment des autres langages, et vous comptez sur del pour libérer la mémoire quand ce n'est pas le cas. Je ne sais pas si c'est mieux, mais dans mon propre code, j'ai surmonter cela en réaffectant la variable à un objet None ou vide.
POUR VOTRE EXEMPLE SPÉCIFIQUE - ÉDITION MINIMALE DE CODE
Tant que vous pouvez stocker vos grandes données en mémoire - deux fois, je pense que vous pouvez faire ce que vous essayez de faire en changeant simplement une seule ligne. J'ai écrit un code très similaire et cela a fonctionné pour moi lorsque j'ai réaffecté la variable (vice call del ou tout type de ramasse-miettes). Si cela ne fonctionne pas, vous devrez peut-être suivre les suggestions ci-dessus et utiliser les E/S disque:
#### earlier code all the same
# clear memory by reassignment (not del or gc)
gen_matrix_df = {}
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
#del gen_matrix_df_list # I suspect you don't even need this, memory will free when the pool is closed
p.close()
p.join()
#### later code all the same
POUR VOTRE EXEMPLE SPÉCIFIQUE - UTILISATION OPTIMALE DE LA MÉMOIRE
Tant que vous pouvez stocker vos grandes données en mémoire - une fois, et que vous avez une idée de la taille de votre fichier, vous pouvez utiliser Pandas read_csv lecture partielle de fichier , à lire seulement nrows à la fois si vous voulez vraiment micro-gérer la quantité de données lues, ou une [quantité fixe de mémoire à la fois en utilisant chunksize], qui renvoie un itérateur 5 . J'entends par là que le paramètre nrows est juste une lecture unique: vous pouvez l'utiliser pour obtenir un aperçu d'un fichier, ou si pour une raison quelconque vous vouliez que chaque partie ait exactement le même nombre de lignes (parce que, par exemple, si l'une de vos données est une chaîne de longueur variable, chaque ligne ne prendra pas la même quantité de mémoire). Mais je pense que dans le but de préparer un fichier pour le multitraitement, il sera beaucoup plus facile d'utiliser des morceaux, car cela concerne directement la mémoire, ce qui est votre préoccupation. Il sera plus facile d'utiliser les essais et les erreurs pour tenir dans la mémoire en fonction de blocs de taille spécifique que le nombre de lignes, ce qui changera la quantité d'utilisation de la mémoire en fonction de la quantité de données dans les lignes. La seule autre partie difficile est que, pour une raison spécifique à l'application, vous regroupez des lignes, ce qui rend les choses un peu plus compliquées. En utilisant votre code comme exemple:
'''load the genome matrix file onto pandas as dataframe.
This makes is more easy for multiprocessing'''
# store the splitted dataframes as list of key, values(pandas dataframe) pairs
# this list of dataframe will be used while multiprocessing
#not sure why you need the ordered dict here, might add memory overhead
#gen_matrix_df_list = collections.OrderedDict()
#a defaultdict won't throw an exception when we try to append to it the first time. if you don't want a default dict for some reason, you have to initialize each entry you care about.
gen_matrix_df_list = collections.defaultdict(list)
chunksize = 10 ** 6
for chunk in pd.read_csv(genome_matrix_file, sep='\t', names=header, chunksize=chunksize)
# now, group the dataframe by chromosome/contig - so it can be multiprocessed
gen_matrix_df = chunk.groupby('CHROM')
for chr_, data in gen_matrix_df:
gen_matrix_df_list[chr_].append(data)
'''Having sorted chunks on read to a list of df, now create single data frames for each chr_'''
#The dict contains a list of small df objects, so now concatenate them
#by reassigning to the same dict, the memory footprint is not increasing
for chr_ in gen_matrix_df_list.keys():
gen_matrix_df_list[chr_]=pd.concat(gen_matrix_df_list[chr_])
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
p.close()
p.join()