55 votes

Pourquoi la chaîne commence-t-elle plus lentement que dedans?

Étonnamment, je trouve startswith plus lente que l' 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

Comme nous le savons tous, l' in besoins de l'opération de recherche de l'ensemble de la chaîne et de l' startswith juste besoin de vérifier les premiers caractères, startswith devrait être plus efficace.

Lors de l' s est assez grand, startswith est plus rapide:

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

Il semble donc que l'appelant startswith a une surcharge, ce qui le rend plus lent lorsque la chaîne est faible.

Et que j'ai essayé de comprendre ce qu'est la surcharge de l' startswith appel.

Tout d'abord, j'ai utilisé un f variable pour réduire le coût de la dot de l'opération - comme indiqué dans cette réponse - là, on peut voir startswith est encore plus lent:

In [16]: f=s.startswith

In [17]: %timeit f("XYZ")
1000000 loops, best of 3: 270 ns per loop

De plus, j'ai testé le coût d'un vide appel de la fonction:

In [18]: def func(a): pass

In [19]: %timeit func("XYZ")
10000000 loops, best of 3: 106 ns per loop

Quel que soit le coût de la dot de fonctionnement et d'appel de fonction, le temps d' startswith (270-106)=164ns, mais l' in opération ne prend que de 81,7 ns. Il semble qu'il y a encore quelques frais pour startswith, c'est quoi?

Ajouter le résultat du test entre startswith et __contains__ comme suggéré par poke et 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

38voto

poke Points 64398

Comme déjà mentionné dans les commentaires, si vous utilisez s.__contains__("XYZ") , vous obtenez un résultat qui est plus semblable à l' s.startswith("XYZ") parce qu'il a besoin de prendre le même chemin: la recherche de Membre sur la chaîne de l'objet, suivi d'un appel de fonction. C'est généralement un peu cher (pas assez de ce que vous devriez vous inquiéter, bien sûr). Sur l'autre main, lorsque vous n' "XYZ" in s, l'analyseur interprète l'opérateur et peut couper l'accès des membres à l' __contains__ (ou plutôt la mise en œuvre derrière elle, car __contains__ lui-même est juste un moyen d'accéder à la mise en œuvre).

Vous pouvez vous faire une idée en regardant le pseudo-code:

>>> 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

De sorte qu'en comparant s.__contains__("XYZ") avec s.startswith("XYZ") va produire un résultat similaire, cependant, pour votre exemple de chaîne s, startswith sera toujours plus lent.

Pour l'obtenir, vous pouvez vérifier la mise en œuvre des deux. Intéressant à voir pour les contient de la mise en œuvre , c'est qu'il est statiquement typé, et juste suppose que l'argument est une unicode objet lui-même. Donc, c'est assez efficace.

L' startswith mise en œuvre est cependant une "dynamique" Python méthode qui nécessite la mise en œuvre de réellement analyser les arguments. startswith prend également en charge un tuple comme un argument, ce qui rend l'ensemble de la start-up de la méthode un peu plus lent: (raccourcie par moi, avec mes commentaires):

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);
}

C'est probablement une des raisons pour lesquelles startswith est plus lent pour les chaînes pour lesquelles un contains est rapide en raison de sa simplicité.

1voto

Cyphase Points 741

Cela est probable parce que str.startswith() fait plus que str.__contains__() , et aussi parce que je crois que str.__contains__ fonctionne entièrement en C, alors que str.startswith() doit interagir avec les types Python. Sa signature est str.startswith(prefix[, start[, end]]) , où préfixe peut être un tuple de chaînes à essayer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X