为什么字符串的开始比慢?

令人惊讶的是,我发现startswithin

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

大家都知道, in操作需要搜索整个字符串, startswith只需要检查的前几个字符,所以startswith应该更加高效。

s足够大, startswith更快:

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

所以看起来,调用startswith有一些开销,这使得字符串很小时会变慢。

而且我试图弄清楚什么是startswith调用的开销。

首先,我使用了一个f变量来降低点操作的成本 - 正如在这个答案中提到的 - 在这里我们可以看到startswith仍然比较慢:

In [16]: f=s.startswith

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

此外,我测试了一个空函数的成本:

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

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

不管点的操作和函数调用的成本,时间startswith大约为(270-106)= 164ns,但in只有81.7ns操作需要。 似乎startswith还有一些开销,那是什么?

添加的测试结果startswith__contains__被捅和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

正如已经在注释中提到的那样,如果使用s.__contains__("XYZ")则会得到与s.startswith("XYZ")更类似的结果,因为它需要采用相同的路由:字符串对象上的成员查找,然后是一个函数调用。 这通常比较昂贵(当然,你不应该担心)。 另一方面,当你"XYZ" in s执行"XYZ" in s ,解析器解释运算符,并且可以__contains__成员对__contains__访问(或者说它的后面的实现,因为__contains__本身只是访问实现的一种方式) 。

您可以通过查看字节码来了解这一点:

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

因此,比较s.__contains__("XYZ")s.startswith("XYZ")会产生更相似的结果,但是对于您的示例字符串sstartswith仍然会变慢。

为了解决这个问题,你可以检查两者的实现。 包含实现的有趣之处在于它是静态类型的,并且假定参数是一个unicode对象本身。 所以这非常有效。

然而startswith实现是一个“动态”的Python方法,它需要实现来实际解析参数。 startswith还支持一个元组作为参数,这使得该方法的整个启动速度变慢一点:(由我和我的评论缩短):

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

这可能是一个很大的原因startswith是字符串,其较慢的contains是因为它的简单快捷。


这很可能是因为str.startswith()str.__contains__()更多str.__contains__() ,并且也因为我相信str.__contains__在C中完全运行,而str.startswith()必须与Python类型进行交互。 它的签名是str.startswith(prefix[, start[, end]]) ,其中prefix可以是要尝试的字符串元组。

链接地址: http://www.djcxy.com/p/31603.html

上一篇: Why is string's startswith slower than in?

下一篇: Why is it slower to iterate over a small string than a small list?