为什么int(len(str(k)))比循环快(while)

时间:2018-11-26 12:51:00

标签: python c performance

我想知道为什么这个功能:

def digit(k):
    return len(str(k))

比这快吗? :

def digit(k):
    i = 0
    while k != 0:
        k = k // 10
        i += 1
    return i

为什么在C语言中却相反?

1 个答案:

答案 0 :(得分:4)

让我们看看如果我们采用您的Python代码并将其尽可能地转换为C会发生什么。我们可以使用Cython非常轻松地做到这一点:

# save this in a file named "testmod.pyx" and compile it with Cython and a
# C compiler - details vary depending on OS and Python installation

from libc.stdio cimport snprintf
from libc.string cimport strlen

def c_digit_loop(k_):
    cdef unsigned int k = k_
    cdef int i = 0
    while k != 0:
        k = k // 10
        i += 1
    return i

def c_digit_str(k_):
    cdef unsigned int k = k_
    cdef char strbuf[32] # more than enough for any 'unsigned int'
    snprintf(strbuf, sizeof(strbuf), "%u", k);
    return strlen(strbuf);

您从中获得的机器代码虽然不是最佳的,但是已经足够进行快速测试。这样,我们就可以使用timeit直接比较效果,如下所示:

# save this in a file named 'test.py' and run it using the
# same CPython you compiled testmod.pyx against

import timeit
from testmod import c_digit_loop, c_digit_str

def py_digit_loop(k):
    i = 0
    while k != 0:
        k = k // 10
        i += 1
    return i

def py_digit_str(k):
    return len(str(k))

def test1(name):
    print(name, timeit.timeit(name+"(1234567)", "from __main__ import "+name,
                              number=10000))

test1("py_digit_loop")
test1("py_digit_str")
test1("c_digit_str")
test1("c_digit_loop")

运行该程序时,这是我在键入此内容的计算机上得到的输出。我已经手动将数字排列起来,以使其更容易通过眼睛进行比较。

py_digit_loop 0.004024484000183293
py_digit_str  0.0020454510013223626
c_digit_str   0.0009924650003085844
c_digit_loop  0.00025072999960684683

这样可以确认您的原始断言:循环比在Python中转换为字符串要慢,但在C语言中则相反。但是请注意,在C中转换为字符串仍然比在Python中转换为字符串更快。

要准确地了解为什么会发生这种情况,我们需要比今天早上更深入地研究Python解释器的内胆,但是我已经足够了解它的内胆了,可以告诉您大纲。 CPython解释器不是很有效。甚至对小整数的操作都涉及引用计数和堆上暂存对象的构造。在Python中执行基本算术的循环,每次迭代需要一个或两个临时对象(取决于0、1、2 ...是否被“内联”)。通过转换为字符串并取其长度进行计算涉及整个计算仅创建一个一个临时对象,即字符串。对于这两个Python实现,与这些暂存对象有关的簿记使实际计算的成本相形见

基于C字符串的实现几乎执行与基于Python字符串的实现相同的步骤,但是其暂存对象是堆栈上的char数组,而不是完整的Python字符串对象,并且显然,所有这些本身都可以使速度提高40-50%。

基于C循环的实现可将实际循环编译为 8条机器指令。没有内存访问。甚至没有硬件除法指令(这是strength reduction的魅力)。然后还有数百条关于Python对象模型的指令。 0.00025秒中的大多数时间仍然是开销