empty dictionaries dict create python python-2.7 python-3.x dictionary python-internals

dictionaries - python dictionary methods



¿Por qué la definición de dict es más rápida en Python 2.7 que en Python 3.x? (3)

Como @Kevin ya declaró:

CPython no está diseñado para ser rápido en un sentido absoluto. Está diseñado para ser escalable

Pruebe esto en su lugar:

$ python -mtimeit "dict([(2,3)]*10000000)" 10 loops, best of 3: 512 msec per loop $ $ python3 -mtimeit "dict([(2,3)]*10000000)" 10 loops, best of 3: 502 msec per loop

Y otra vez:

$ python -mtimeit "dict([(2,3)]*100000000)" 10 loops, best of 3: 5.19 sec per loop $ $ python3 -mtimeit "dict([(2,3)]*100000000)" 10 loops, best of 3: 5.07 sec per loop

Eso muestra que no se puede comparar Python3 como perder contra Python2 en una diferencia tan insignificante. Desde el punto de vista de las cosas, Python3 debería escalar mejor.

Me encontré con una situación (no muy inusual) en la que tuve que usar un map() o una lista de expresiones de comprensión. Y luego me pregunté cuál es más rápido.

This respuesta de StackOverflow me proporcionó la solución, pero luego comencé a probarla yo mismo. Básicamente, los resultados fueron los mismos, pero encontré un comportamiento inesperado al cambiar a Python 3 por lo que sentí curiosidad, y es decir:

λ iulian-pc ~ → python --version Python 2.7.6 λ iulian-pc ~ → python3 --version Python 3.4.3 λ iulian-pc ~ → python -mtimeit ''{}'' 10000000 loops, best of 3: 0.0306 usec per loop λ iulian-pc ~ → python3 -mtimeit ''{}'' 10000000 loops, best of 3: 0.105 usec per loop λ iulian-pc ~ → python -mtimeit ''dict()'' 10000000 loops, best of 3: 0.103 usec per loop λ iulian-pc ~ → python3 -mtimeit ''dict()'' 10000000 loops, best of 3: 0.165 usec per loop

Tenía la suposición de que Python 3 es más rápido que Python 2, pero resultó en varias publicaciones ( 1 , 2 ) que no es el caso. Entonces pensé que quizás Python 3.5 funcionaría mejor en una tarea tan simple, como lo indican en su README :

El lenguaje es prácticamente el mismo, pero muchos detalles, especialmente el funcionamiento de objetos incorporados como diccionarios y cadenas de caracteres, han cambiado considerablemente y finalmente se han eliminado muchas características obsoletas.

Pero no, lo hizo aún peor:

λ iulian-pc ~ → python3 --version Python 3.5.0 λ iulian-pc ~ → python3 -mtimeit ''{}'' 10000000 loops, best of 3: 0.144 usec per loop λ iulian-pc ~ → python3 -mtimeit ''dict()'' 1000000 loops, best of 3: 0.217 usec per loop

He intentado profundizar en el código fuente de Python 3.5 para dict , pero mi conocimiento del lenguaje C no es suficiente para encontrar la respuesta yo mismo (o, tal vez, incluso no busco en el lugar correcto).

Entonces, mi pregunta es:

¿Qué hace que la versión más nueva de Python sea más lenta en comparación con una versión anterior de Python en una tarea relativamente simple como una definición dict , ya que por el sentido común debería ser al revés? Soy consciente del hecho de que estas diferencias son tan pequeñas que en la mayoría de los casos pueden descuidarse. Fue solo una observación que me hizo sentir curiosidad acerca de por qué el tiempo aumentó y al menos no se mantuvo igual.


disassemble {} :

>>> from dis import dis >>> dis(lambda: {}) 1 0 BUILD_MAP 0 3 RETURN_VALUE

Implementación de Python 2.7 de BUILD_MAP

TARGET(BUILD_MAP) { x = _PyDict_NewPresized((Py_ssize_t)oparg); PUSH(x); if (x != NULL) DISPATCH(); break; }

Implementación Python 3.5 de BUILD_MAP

TARGET(BUILD_MAP) { int i; PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg); if (map == NULL) goto error; for (i = oparg; i > 0; i--) { int err; PyObject *key = PEEK(2*i); PyObject *value = PEEK(2*i - 1); err = PyDict_SetItem(map, key, value); if (err != 0) { Py_DECREF(map); goto error; } } while (oparg--) { Py_DECREF(POP()); Py_DECREF(POP()); } PUSH(map); DISPATCH(); }

Es un poco más código.

EDITAR:

La implementación de Python 3.4 de BUILD_MAP es exactamente igual a 2.7 (gracias @ user2357112). Excavo más profundo y parece que Python tiene 3 min. De dict y 8 PyDict_MINSIZE_COMBINED const

PyDict_MINSIZE_COMBINED es el tamaño de inicio para cualquier dict nuevo, no dividido. 8 permite dictados con no más de 5 entradas activas; los experimentos sugirieron que esto es suficiente para la mayoría de los dicts (que consisten principalmente en dicts generalmente pequeños creados para pasar argumentos de palabra clave). Al hacer esto 8, en lugar de 4, se reduce el número de tamaños para la mayoría de los diccionarios, sin ningún uso significativo de memoria adicional.

Mira _PyDict_NewPresized en Python 3.4

PyObject * _PyDict_NewPresized(Py_ssize_t minused) { Py_ssize_t newsize; PyDictKeysObject *new_keys; for (newsize = PyDict_MINSIZE_COMBINED; newsize <= minused && newsize > 0; newsize <<= 1) ; new_keys = new_keys_object(newsize); if (new_keys == NULL) return NULL; return new_dict(new_keys, NULL); }

y en 2.7

PyObject * _PyDict_NewPresized(Py_ssize_t minused) { PyObject *op = PyDict_New(); if (minused>5 && op != NULL && dictresize((PyDictObject *)op, minused) == -1) { Py_DECREF(op); return NULL; } return op; }

En ambos casos, minused tiene valor 1.

Python 2.7 crea un dict vacío y Python 3.4 crea un dict de 7 elementos.


Porque a nadie le importa

Las diferencias que está citando están en el orden de decenas o cientos de nanosegundos. Una ligera diferencia en la forma en que el compilador de C optimiza el uso del registro podría causar fácilmente tales cambios (como podría ocurrir con cualquier cantidad de otras diferencias de optimización del nivel C). Eso, a su vez, podría ser causado por cualquier cantidad de cosas, como cambios en el número y uso de variables locales en la implementación C de Python (CPython), o incluso simplemente cambiando los compiladores C.

El hecho es que nadie está optimizando activamente estas pequeñas diferencias, por lo que nadie podrá darle una respuesta específica. CPython no está diseñado para ser rápido en un sentido absoluto. Está diseñado para ser escalable . Entonces, por ejemplo, puede meter cientos o miles de elementos en un diccionario y continuará funcionando bien. Pero la velocidad absoluta de crear un diccionario simplemente no es una preocupación principal de los implementadores de Python, al menos cuando las diferencias son tan pequeñas.