web-dev-qa-db-fra.com

Diminuer la taille des objets cPickle

J'exécute du code qui crée de gros objets, contenant plusieurs classes définies par l'utilisateur, que je dois ensuite sérialiser pour une utilisation ultérieure. D'après ce que je peux dire, seul le décapage est suffisamment polyvalent pour mes besoins. J'utilise cPickle pour les stocker, mais les objets qu'il génère mesurent environ 40 Go, à partir d'un code qui s'exécute dans 500 Mo de mémoire. La vitesse de sérialisation n'est pas un problème, mais la taille de l'objet l'est. Y a-t-il des conseils ou des procédés alternatifs que je peux utiliser pour réduire la taille des cornichons?

31
ddn

Si vous devez utiliser du cornichon et qu'aucune autre méthode de sérialisation ne fonctionne pour vous, vous pouvez toujours diriger le cornichon par bzip2. Le seul problème est que bzip2 est un peu lent ... gzip devrait être plus rapide, mais la taille du fichier est presque 2x plus grande:

In [1]: class Test(object):
            def __init__(self):
                self.x = 3841984789317471348934788731984731749374
                self.y = 'kdjsaflkjda;sjfkdjsf;klsdjakfjdafjdskfl;adsjfl;dasjf;ljfdlf'
        l = [Test() for i in range(1000000)]

In [2]: import cPickle as pickle          
        with open('test.pickle', 'wb') as f:
            pickle.dump(l, f)
        !ls -lh test.pickle
-rw-r--r--  1 viktor  staff    88M Aug 27 22:45 test.pickle

In [3]: import bz2
        import cPickle as pickle
        with bz2.BZ2File('test.pbz2', 'w') as f:
            pickle.dump(l, f)
        !ls -lh test.pbz2
-rw-r--r--  1 viktor  staff   2.3M Aug 27 22:47 test.pbz2

In [4]: import gzip
        import cPickle as pickle
        with gzip.GzipFile('test.pgz', 'w') as f:
            pickle.dump(l, f)
        !ls -lh test.pgz
-rw-r--r--  1 viktor  staff   4.8M Aug 27 22:51 test.pgz

Nous voyons donc que la taille du fichier de bzip2 est presque 40 fois plus petit, gzip est 20 fois plus petit. Et gzip est assez proche des performances du cPickle brut, comme vous pouvez le voir:

cPickle : best of 3: 18.9 s per loop
bzip2   : best of 3: 54.6 s per loop
gzip    : best of 3: 24.4 s per loop
38
Viktor Kerkez

Vous pouvez combiner votre appel cPickle dump avec un fichier zip:

import cPickle
import gzip

def save_zipped_pickle(obj, filename, protocol=-1):
    with gzip.open(filename, 'wb') as f:
        cPickle.dump(obj, f, protocol)

Et pour recharger un objet mariné zippé:

def load_zipped_pickle(filename):
    with gzip.open(filename, 'rb') as f:
        loaded_object = cPickle.load(f)
        return loaded_object
39
jozzas

Vous voudrez peut-être utiliser un protocole de décapage plus efficace.

À l'heure actuelle, il existe trois protocoles de pickle :

  • La version 0 du protocole est le protocole ASCII et est rétrocompatible avec les versions antérieures de Python.
  • Le protocole version 1 est l'ancien format binaire qui est également compatible avec les versions antérieures de Python.
  • Le protocole version 2 a été introduit dans Python 2.3. Il fournit un décapage beaucoup plus efficace des classes de nouveau style.

et de plus, la valeur par défaut est le protocole 0, le moins efficace:

Si aucun protocole n'est spécifié, le protocole 0 est utilisé. Si le protocole est spécifié comme une valeur négative ou HIGHEST_PROTOCOL, la version de protocole la plus élevée disponible sera utilisée.

Vérifions la différence de taille entre l'utilisation du dernier protocole, qui est actuellement le protocole 2 (le plus efficace) et l'utilisation du protocole 0 (par défaut) pour un exemple arbitraire. Notez que j'utilise protocol = -1 ici, pour nous assurer que nous utilisons toujours le dernier protocole, et que j'importe cPickle pour nous assurer que nous utilisons l'implémentation C plus rapide:

import numpy
from sys import getsizeof
import cPickle as pickle

# Create list of 10 arrays of size 100x100
a = [numpy.random.random((100, 100)) for _ in xrange(10)]

# Pickle to a string in two ways
str_old = pickle.dumps(a, protocol=0)
str_new = pickle.dumps(a, protocol=-1)

# Measure size of strings
size_old = getsizeof(str_old)
size_new = getsizeof(str_new)

# Print size (in kilobytes) using old, using new, and the ratio
print size_old / 1024.0, size_new / 1024.0, size_old / float(size_new)

L'impression que je reçois est:

2172.27246094 781.703125 2.77889698975

Indiquant que le décapage utilisant l'ancien protocole a utilisé 2172 Ko, le décapage utilisant le nouveau protocole a utilisé 782 Ko et la différence est un facteur x2,8. Notez que ce facteur est spécifique à cet exemple - vos résultats peuvent varier en fonction de l'objet que vous décapez.

2
Moot