values true print false characters python python-2.7 cpython python-internals startswith

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.