「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。
python的标准库有个叫做timeit的库。它可以被用来测试代码的运行速度。
如果我们使用 Ipython 这种交互式的 python 解释器,那么可以通过一个简单的操作测试代码的运行速度。 具体操作像下面这样(这里假设测试以下 numpy 数组自带的排序效率):
%%timeit
np.random.randint(1000,size=1000).sort()
运行这段代码之后,下方的输出会显示类似下面的字样,指示通过多次运行得到大致运行时间
38.2 µs ± 2.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
但是一个显然的事实是它只能做到一个简单的运行时间显示。可操作性不强。 为了更好地做一些测试,我们可以尝试使用timeit模块对外提供的模块。
首先,与我们在ipython中使用的类似,timeit提供了同名的函数timeit用于测试代码运行速率。我们只需要为它传入一段可执行的代码,它就会返回一个多次运行求出的平均运行时间。
timeit("[0]",number=100)
这里指定number语句的原因是默认的运行次数太多(1000000次),在简单语句的测试时还让人能够接受,但当我们操作的数据过多,运行时间稍微长一点,这就是让人不可接受的了。因此number被设定成了100。
在这种字符串式的代码片段中,它是不支持外部定义的类和函数之类的东西的。应该说这样认为,这个字符串代码可以当作一个单独的作用域。如果直接使用自定义的函数,那么将会出现无法name not found的错误。 当然我们可以用三引号文本块,然后写import语句。
def foo():
[0]
timeit("""
from __main__ import foo
foo()
""", number=100)
只是每次测试都额外做导入也不太好。这属于额外开销了,timeit也考虑到了这一点,因此它额外有一个setup的参数,这样可以把导包的语句写到setup参数中
timeit("""
foo()
""", number=100,setup="from __main__ import foo")
这样就避免了额外开销。
除此之外,timeit还提供了另一种更好用的测试方式,通过传入一个函数做测试。 像下面这样
timeit(foo, number=100)
不过这个函数不能传参数,它应该作为一个无参函数被调用。对应简单的测试代码片段,这是没什么问题,但是如果我们需要做对比的话,就需要使用python中的闭包了。毕竟对比讲究的是控制变量。 比如说我们测试python的list和numpy数组的排序速度。
def test_function_gen(length: int):
x = np.random.randint(0, 10000, size=1000 * (length + 1))
def test_list():
arr = x.tolist()
arr.sort()
def test_numpy():
arr = x.copy()
arr.sort()
return test_list, test_numpy
def benchmark(func):
duration = timeit.Timer(func).timeit(number=num_runs)
return duration / num_runs
这里用闭包生成测试函数,之后准备交由benchmark测试时间 我们测试下1000到10000的排序时间
result = []
for epoch in range(10):
num_runs = 20
python_list_test, numpy_array_test = test_function_gen(epoch)
t1 = benchmark(python_list_test)
t2 = benchmark(numpy_array_test)
result.append((t1, t2))
list_times, numpy_times = np.array(result).T
label_x = np.arange(1000, 10001, 1000)
plt.plot(label_x, list_times, '-b', label="python list")
plt.plot(label_x, numpy_times, '--r', label="numpy ndarray")
plt.legend()
plt.show()
顺便用matplotlib画下示意图
到这里我们就完成程序的运行时间测试了。