我想知道为什么这个函数:
def digit(k):
return len(str(k))
比这个快吗?:
def digit(k):
i = 0
while k != 0:
k = k // 10
i += 1
return i
为什么它是相反的,例如在C中?
让我们看看如果我们把你的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中执行基本运算的循环每次迭代需要一到两个scratch对象(取决于0、1、2…是否为"interned"(。通过转换为字符串并获取其长度来进行计算,只需要为整个计算创建一个临时对象字符串。这些scratch对象所涉及的记账使实际计算的成本相形见绌,对于两个Python实现。
基于C字符串的实现执行的步骤与基于Python字符串的实现几乎完全相同,但它的scratch对象是堆栈上的char
数组,而不是一个完整的Python字符串对象,这一切本身显然有利于40-50%的加速。
基于C循环的实现为实际循环编译了8条机器指令。没有内存访问。甚至连硬件划分指令都没有(这就是强度降低的魔力(。然后还有数百条关于Python对象模型的指令。这0.00025秒中的大部分仍在头顶。