Comme le collection
diminue au même rythme que rows
, votre consommation de mémoire reste stable.L'appel gc.collect()
ne va pas faire beaucoup de différence.
La gestion de la mémoire dans CPython est subtile. Le fait de supprimer des références et d'exécuter un cycle de collecte ne signifie pas nécessairement que la mémoire sera renvoyée au système d'exploitation. Voir this answer for details. Pour vraiment économiser de la mémoire, vous devez structurer ce code autour des générateurs et des itérateurs au lieu de grandes listes d'éléments. Je suis très surpris que vous ayez des délais d'attente de connexion car la récupération de toutes les lignes ne devrait pas prendre beaucoup plus de temps que d'aller chercher une ligne à la fois et d'effectuer le traitement simple que vous faites. Peut-être devrions-nous jeter un oeil à votre code db-fetching?
Si le traitement de ligne à la fois n'est vraiment pas une possibilité, gardez au moins vos données comme une variable non modifiable et effectuez tous les traitements avec des générateurs et des itérateurs.
Je vais décrire ces différentes approches.
D'abord, certaines fonctions communes:
# if you don't need random-access to elements in a sequence
# a deque uses less memory and has faster appends and deletes
# from both the front and the back.
from collections import deque
from itertools import izip, repeat, islice, chain
import re
re_redshift_chars = re.compile(r'[abcdefg]')
def istrjoin(sep, seq):
"""Return a generator that acts like sep.join(seq), but lazily
The separator will be yielded separately
"""
return islice(chain.from_iterable(izip(repeat(sep), seq)), 1, None)
def escape_redshift(s):
return re_redshift_chars.sub(r'\\\g<0>', s)
def tabulate(row):
return "\t".join(escape_redshift(str(v)) if v is not None else '' for v in row)
Maintenant, l'idéal est rangée à-un-temps de traitement, comme ceci:
cursor = db.cursor()
cursor.execute("""SELECT * FROM bigtable""")
rowstrings = (tabulate(row) for row in cursor.fetchall())
lines = istrjoin("\n", rowstrings)
file_like_obj.writelines(lines)
cursor.close()
Cela prendra le moins possible mémoire - seulement une ligne à la fois.
Si vous avez vraiment besoin de stocker l'ensemble de résultats, vous pouvez modifier le code légèrement:
cursor = db.cursor()
cursor.execute("SELECT * FROM bigtable")
collection = deque(cursor.fetchall())
cursor.close()
rowstrings = (tabulate(row) for row in collection)
lines = istrjoin("\n", rowstrings)
file_like_obj.writelines(lines)
Maintenant, nous nous réunissons tous les résultats dans collection
premier qui reste entièrement en mémoire pour l'ensemble de l'exécution du programme. Cependant, nous pouvons également dupliquer votre approche de suppression des éléments de collection tels qu'ils sont utilisés. Nous pouvons garder la même "forme de code" en créant un générateur que vide sa collection source comme cela fonctionne. Il ressemblerait à quelque chose comme ceci:
def drain(coll):
"""Return an iterable that deletes items from coll as it yields them.
coll must support `coll.pop(0)` or `del coll[0]`. A deque is recommended!
"""
if hasattr(coll, 'pop'):
def pop(coll):
try:
return coll.pop(0)
except IndexError:
raise StopIteration
else:
def pop(coll):
try:
item = coll[0]
except IndexError:
raise StopIteration
del coll[0]
return item
while True:
yield pop(coll)
Maintenant, vous pouvez facilement remplacer drain(collection)
pour collection
lorsque vous souhaitez libérer de la mémoire que vous allez. Après drain(collection)
est épuisé, l'objet collection
sera vide.
Je ne veux pas être sarcastique mais pourquoi ne pas simplement l'essayer et voir? –
pensé que quelqu'un pourrait savoir si cela en vaut la peine avec les frais généraux du GC ou des trucs internes python en cours que j'aurais oublié. – tipu
Si cela est possible, il pourrait être utile d'examiner la possibilité d'utiliser des itérateurs et de traiter tous les tuples de 40 k à la fois, en construisant la liste et en les traitant en même temps. Cela ajoutera de la complexité et ne vaudra peut-être pas la peine. – Moshe