python - ¿Por qué es repr(int) más rápido que str(int)?
performance python-internals (3)
Me pregunto por qué repr(int)
es más rápido que str(int)
. Con el siguiente fragmento de código:
ROUNDS = 10000
def concat_strings_str():
return ''''.join(map(str, range(ROUNDS)))
def concat_strings_repr():
return ''''.join(map(repr, range(ROUNDS)))
%timeit concat_strings_str()
%timeit concat_strings_repr()
Obtengo estos tiempos (Python 3.5.2, pero resultados muy similares con 2.7.12):
1.9 ms ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.38 ms ± 9.07 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Si estoy en el camino correcto, se llama a la misma función long_to_decimal_string
debajo del capó.
¿Obtuve algo mal o qué más está pasando que me estoy perdiendo?
actualización : Esto probablemente no tiene nada que ver con los métodos __repr__
o __str__
int
, pero con las diferencias entre repr()
y str()
, ya que int.__str__
e int.__repr__
son, de hecho, int.__repr__
rápidos:
def concat_strings_str():
return ''''.join([one.__str__() for one in range(ROUNDS)])
def concat_strings_repr():
return ''''.join([one.__repr__() for one in range(ROUNDS)])
%timeit concat_strings_str()
%timeit concat_strings_repr()
resultados en:
2.02 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.05 ms ± 7.07 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Acabo de comparar las implementaciones str
y repr
en la rama 3.5. Mira here .
Debido a que el uso de str(obj)
primero debe pasar por type.__call__
then str.__new__
(crea una nueva cadena) luego PyObject_Str
( PyObject_Str
una cadena fuera del objeto) que invoca int.__str__
y, finalmente , utiliza la función que has vinculado.
repr(obj)
, que corresponde a builtin_repr
, llama directamente a PyObject_Repr
(obtiene el objeto repr) que luego llama a int.__repr__
que usa la misma función que int.__str__
.
Además, la ruta que toman a través de call_function
(la función que maneja el CALL_FUNCTION
operación CALL_FUNCTION
que se genera para las llamadas) es ligeramente diferente.
Desde la rama principal en GitHub (CPython 3.7):
-
str
pasa por_PyObject_FastCallKeywords
(que es el que llama altype.__call__
). Además de realizar más controles, esto también necesita crear una tupla para contener los argumentos posicionales (ver_PyStack_AsTuple
). -
repr
pasa por_PyCFunction_FastCallKeywords
que llama_PyMethodDef_RawFastCallKeywords
.repr
también tiene suerte porque, dado que solo acepta un único argumento (el cambio lo lleva al caso_PyMethodDef_RawFastCallKeywords
en_PyMethodDef_RawFastCallKeywords
) no es necesario crear una tupla, solo indexar los argumentos .
Como dice su actualización, esto no se trata de int.__repr__
vs int.__str__
, son la misma función después de todo; se trata de cómo repr
y str
llegan a ellos. str
solo necesita trabajar un poco más duro.
Hay varias posibilidades porque las funciones CPython que son responsables del str
y repr
return son ligeramente diferentes.
Pero supongo que la razón principal es que str
es un type
(una clase) y el método str.__new__
tiene que llamar a __str__
mientras que repr
puede ir directamente a __repr__
.