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.
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
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.