true - python string format replace
¿Por qué el inicio de la cadena es más lento que en? (2)
Como ya se mencionó en los comentarios, si usa
s.__contains__("XYZ")
obtendrá un resultado que es más similar a
s.startswith("XYZ")
porque necesita tomar la misma ruta: búsqueda de miembros en el objeto de cadena , seguido de una llamada a la función.
Esto suele ser algo costoso (no es suficiente por lo que deba preocuparse, por supuesto).
Por otro lado, cuando hace
"XYZ" in s
, el analizador interpreta al operador y puede acortar el acceso de los miembros a
__contains__
(o más bien la implementación detrás de él, porque
__contains__
es solo una forma de acceder a la implementación) .
Puede hacerse una idea al mirar el código de bytes:
>>> dis.dis(''"XYZ" in s'')
1 0 LOAD_CONST 0 (''XYZ'')
3 LOAD_NAME 0 (s)
6 COMPARE_OP 6 (in)
9 RETURN_VALUE
>>> dis.dis(''s.__contains__("XYZ")'')
1 0 LOAD_NAME 0 (s)
3 LOAD_ATTR 1 (__contains__)
6 LOAD_CONST 0 (''XYZ'')
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 RETURN_VALUE
Por lo tanto, comparar
s.__contains__("XYZ")
con
s.startswith("XYZ")
producirá un resultado más similar, sin embargo, para su cadena de ejemplo
s
, el
startswith
será aún más lento.
Para llegar a eso, puede verificar la implementación de ambos. Es interesante ver que la implementación contiene es que está tipicamente estática, y solo asume que el argumento es un objeto unicode. Entonces esto es bastante eficiente.
startswith
embargo, el
startswith
implementación
es un método Python "dinámico" que requiere que la implementación analice realmente los argumentos.
startswith
también admite una tupla como argumento, lo que hace que todo el inicio del método sea un poco más lento: (acortado por mí, con mis comentarios):
static PyObject * unicode_startswith(PyObject *self, PyObject *args)
{
// argument parsing
PyObject *subobj;
PyObject *substring;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
int result;
if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
return NULL;
// tuple handling
if (PyTuple_Check(subobj)) {}
// unicode conversion
substring = PyUnicode_FromObject(subobj);
if (substring == NULL) {}
// actual implementation
result = tailmatch(self, substring, start, end, -1);
Py_DECREF(substring);
if (result == -1)
return NULL;
return PyBool_FromLong(result);
}
Es probable que esta sea una gran razón por la que el
startswith
es más lento para las cadenas para las cuales un
startswith
es rápido debido a su simplicidad.
Sorprendentemente, encuentro que el
startswith
es más lento que
in
:
In [10]: s="ABCD"*10
In [11]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 307 ns per loop
In [12]: %timeit "XYZ" in s
10000000 loops, best of 3: 81.7 ns per loop
Como todos sabemos, la operación debe buscar en toda la cadena y
startswith
solo verificar los primeros caracteres, por lo que
startswith
debería ser más eficiente.
Cuando
s
es lo suficientemente grande,
startswith
más rápido:
In [13]: s="ABCD"*200
In [14]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 306 ns per loop
In [15]: %timeit "XYZ" in s
1000000 loops, best of 3: 666 ns per loop
Por lo tanto, parece que las llamadas
startswith
con algo de sobrecarga, lo que lo hace más lento cuando la cadena es pequeña.
Y luego traté de averiguar cuál es la sobrecarga del
startswith
llamada.
Primero, utilicé una variable
f
para reducir el costo de la operación de puntos, como se menciona en esta
answer
, aquí podemos ver que el
startswith
es aún más lento:
In [16]: f=s.startswith
In [17]: %timeit f("XYZ")
1000000 loops, best of 3: 270 ns per loop
Además, probé el costo de una llamada de función vacía:
In [18]: def func(a): pass
In [19]: %timeit func("XYZ")
10000000 loops, best of 3: 106 ns per loop
Independientemente del costo de la operación de punto y la llamada a la función, el tiempo de
startswith
es de aproximadamente (270-106) = 164ns, pero la operación
in
operación solo toma 81.7ns.
Parece que todavía hay algunos gastos generales para
startswith
, ¿qué es eso?
Agregue el resultado de la prueba entre
startswith
y
__contains__
como lo sugieren poke y lvc:
In [28]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 314 ns per loop
In [29]: %timeit s.__contains__("XYZ")
1000000 loops, best of 3: 192 ns per loop
Esto es probable porque
str.startswith()
hace más que
str.__contains__()
, y también porque creo que
str.__contains__
funciona completamente en C, mientras que
str.startswith()
tiene que interactuar con los tipos de Python.
Su firma es
str.startswith(prefix[, start[, end]])
, donde el prefijo puede ser una tupla de cadenas para probar.