Curiosidad sobre __hash__()
Chema Cortes
py en ch3m4.org
Mar Feb 10 02:58:23 CET 2009
El Monday 09 February 2009 17:06:44 Francesc Alted escribió:
> Ya veo. Sin embargo, no entiendo porqué se dice que dividiendo por 8 la
> distribución de hashes es más homegénea (dividir por una constante no
> veo que afecte a la distribución de una función).
Tienes toda la razón. Más que homogénea debería haber dicho más "compacta", ya
que los hashes se concentrarían todos en un rango del dominio.
> Y lo de evitar las
> colisiones, tampoco lo veo claro, ya que al dividir id() por una
> cantidad fija lo máximo que puedes conseguir es hacer que aumenten
> (aunque si es verdad que todas la direcciones de memoria son múltiplos
> de 8, en ese caso la división por esa cantidad no afectaría al número
> de colisiones).
También tienes aquí razón. Las colisiones van a ser las mismas. Tal vez se
debería pensar en algún algoritmo mejor para distribuir los hashes por todo
el dominio.
> De hecho, mis experimentos me dicen que parece que no hay mejora
> aparente con la división por 8:
Yo también he hecho mis experimentos. Se supone que el hash es para mejorar la
velocidad de accesos arbitrarios, así que mi experimento intenta incorporar
algo de aleatoriedad. Para recorrer secuencialmente todo el diccionario, como
haces en tu código, tal vez no se aprecie ninguna mejora.
In [7]: from random import choice
In [8]: class A(object):
...: def __hash__(self): #__hash__ compacto
...: return id(self) >> 3
...:
In [9]: class B(object):
....: def __hash__(self): #__hash__ como entero largo sin signo
....: return id(self)
....:
....:
In [10]: class C(object): #__hash__ por defecto (entero 32bit con signo)
...: pass
...:
In [11]: N = 1000000
In [12]: dA = dict((A(),i) for i in xrange(N))
In [13]: dB = dict((B(),i) for i in xrange(N))
In [14]: dC = dict((C(),i) for i in xrange(N))
In [15]: %time l = [dA[choice(dA.keys())] for i in xrange(1000)]
CPU times: user 122.01 s, sys: 6.07 s, total: 128.08 s
Wall time: 134.27 s
In [16]: %time l = [dB[choice(dB.keys())] for i in xrange(1000)]
CPU times: user 151.80 s, sys: 5.85 s, total: 157.65 s
Wall time: 171.92 s
In [17]: %time l = [dC[choice(dC.keys())] for i in xrange(1000)]
CPU times: user 150.35 s, sys: 5.20 s, total: 155.55 s
Wall time: 160.03 s
En el bugs.python.org se hablaba de ganancias del 10%. Yo estoy consiguiendo
del orden del 18% (y sin optimizar en C).
> O sea, que de manera consistente, la hash sin dividir parece que saca
> más rendimiento (un 15%) que la dividida por 8. No sé, lo mismo el
> rendimiento depende de la plataforma (o no he hecho bien los cálculos).
Estoy convencido que la plataforma tiene bastante influencia. He probado con
tu mismo código (corrigiendo el primer caso con >>3 , y añadiendo el caso del
__hash__ por defecto) y los tiempos que me salen son, respectivamente, 2.081,
2.302 y 1.20. Me sale a la inversa que a tí.
PD: Pruebas hechas en un AMD Sempron 3000+, 64/32bits
PD2 [OT]: Por coincidencias de la vida, estaba leyendo estos días un artículo
del "Communications of ACM" de enero del 2009 titulado "The Long Road To 64
Bits", donde se repasa la lenta y compleja transición que ha sufrido el
tamaño del direccionamiento de los procesadores. ¡Cuánta historia
olvidada...! <http://mags.acm.org/communications/200901/?pg=46>
------------ próxima parte ------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part.
URL: <http://mail.python.org/pipermail/python-es/attachments/20090210/042d02d2/attachment.pgp>
------------ próxima parte ------------
_______________________________________________
Lista de correo Python-es
http://listas.aditel.org/listinfo/python-es
FAQ: http://listas.aditel.org/faqpyes
Más información sobre la lista de distribución Python-es