¿Por qué el inicio de la cadena es más lento que en?

Sorprendentemente, encuentrostartswith es más lento quein:

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, elin la operación necesita buscar en toda la cadena ystartswith solo necesita verificar los primeros caracteres, así questartswith Debería ser más eficiente.

Cuandos es lo suficientemente grandestartswith es 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

Entonces parece que llamarstartswith tiene algo de sobrecarga que lo hace más lento cuando la cadena es pequeña.

Y luego traté de averiguar cuál es la sobrecarga delstartswith llamada.

Primero, usé unf variable para reducir el costo de la operación de puntos, como se menciona en esteresponder - aquí podemos verstartswith sigue siendo 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 de función, el tiempo destartswith es aproximadamente (270-106) = 164ns, pero elin la operación solo requiere 81.7ns. Parece que todavía hay algunos gastos generales parastartswith, ¿que es eso?

Agregue el resultado de la prueba entrestartswith y__contains__ según lo sugerido por 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