web-dev-qa-db-fra.com

Performance du set vs frozenset

Je bricolais avec les types de collection set et frozenset de Python.

Au départ, je pensais que frozenset fournirait une meilleure performance de recherche que set, car immuable, et pourrait donc exploiter la structure des éléments stockés.

Cependant, cela ne semble pas être le cas en ce qui concerne l'expérience suivante:

import random
import time
import sys

def main(n):
    numbers = []
    for _ in xrange(n):
        numbers.append(random.randint(0, sys.maxint))
    set_ = set(numbers)
    frozenset_ = frozenset(set_)

    start = time.time()
    for number in numbers:
        number in set_
    set_duration = time.time() - start

    start = time.time()
    for number in numbers:
        number in frozenset_
    frozenset_duration = time.time() - start

    print "set      : %.3f" % set_duration
    print "frozenset: %.3f" % frozenset_duration


if __== "__main__":
    n = int(sys.argv[1])
    main(n)

J'ai exécuté ce code en utilisant à la fois CPython et PyPy, ce qui a donné les résultats suivants:

> pypy set.py 100000000
set      : 6.156
frozenset: 6.166

> python set.py 100000000
set      : 16.824
frozenset: 17.248

Il semble que frozenset soit en fait plus lent en ce qui concerne les performances de recherche, à la fois dans CPython et dans PyPy. Quelqu'un a-t-il une idée de pourquoi c'est le cas? Je n'ai pas regardé dans les implémentations.

13
Sven Hager

Les implémentations frozenset et set sont largement partagées; une set est simplement une frozenset avec des méthodes de mutation ajoutées, avec la même implémentation de hashtable. Voir le fichier source Objects/setobject.c ; la définition PyFrozenSet_Type de niveau supérieur partage les fonctions avec la définition PySet_Type .

Ici, il n’ya pas d’optimisation pour un groupe de frozens, car il n’est pas nécessaire de calculer les hachages pour les éléments dans la frozenset lorsque vous testez votre adhésion. L'élément que vous utilisez pour tester contre l'ensemble doit encore faire en sorte que son hachage soit calculé afin de trouver le bon emplacement dans la hashtable de l'ensemble afin de pouvoir effectuer un test d'égalité.

En tant que tels, vos résultats de chronométrage sont probablement faussés par d'autres processus en cours d'exécution sur votre système; vous avez mesuré l'heure de l'horloge murale et n'avez pas désactivé la récupération de place Python ni testé la même chose à plusieurs reprises.

Essayez de lancer votre test en utilisant le module timeit , avec une valeur de numbers et une valeur non définie:

import random
import sys
import timeit

numbers = [random.randrange(sys.maxsize) for _ in range(10000)]
set_ = set(numbers)
fset = frozenset(numbers)
present = random.choice(numbers)
notpresent = -1
test = 'present in s; notpresent in s'

settime = timeit.timeit(
    test,
    'from __main__ import set_ as s, present, notpresent')
fsettime = timeit.timeit(
    test,
    'from __main__ import fset as s, present, notpresent')

print('set      : {:.3f} seconds'.format(settime))
print('frozenset: {:.3f} seconds'.format(fsettime))

Cela répète chaque test 1 million de fois et produit:

set      : 0.050 seconds
frozenset: 0.050 seconds
44
Martijn Pieters

La raison des deux types de données différents n’est pas la performance, elle est fonctionnelle. Les frozensets étant immuables, ils peuvent être utilisés comme clé dans les dictionnaires. Les ensembles ne peuvent pas être utilisés à cette fin.

1
T. Durbin