写一手漂亮的代码,走向极致的编程 一、代码运行时间分析

写一手漂亮的代码,何谓漂亮的代码?对我来说大概有这么几点:

写法符合规范(如:该空格的地方打上空格,该换行的地方换行,名命方式符合规范等等)

简洁且可读性高(能十行代码实现并且让人容易看懂的绝不写十一行,对经常重复出现的代码段落进行封装)

性能高(如:运行时间尽可能短,运行时所用内存尽可能少)

要实现以上目标,自然就要对代码进行优化,说到代码的优化,自然而然就会想到对算法时间复杂度进行优化,比如我要实现一个在有序数组中查找一个数,最容易想到的就是遍历一遍 O(n) 的复杂度,优化一下自然是使用二分, O(logn) 的复杂度。如果这段代码在我们的程序中会经常被调用,那么,通过这算法上的优化,我们的程序性能自然而然的会有很高的提升。

但是,有时候会发现,已经对算法进行优化了,程序的性能(如运行时间、内存占用等)仍然不能达到预期,那么,这时候该如何对我们的代码进行进一步的优化呢?

这篇文章将以 Python 为例进行介绍

先来段代码

这里,我将通过使用 Julia 分形的代码来进行。

Julia 集合,由式 \(f_c(z) = z ^2 + c\) 进行反复迭代到。

对于固定的复数 c ,取某一 z 值,可以得到序列

\(z_0, f_c(z_0), f_c(f_c(z_0)), ...\)

这一序列可能发散于无穷大或处于某一范围之内并收敛于某一值,我们将使其不扩散的 z 值的集合称为朱利亚集合。

import time import numpy as np import imageio import PIL import matplotlib.pyplot as plt import cv2 as cv x1, x2, y1, y2 = -1.8, 1.8, -1.8, 1.8 c_real, c_imag = -0.62772, -0.42193 def calculate_z_serial_purepython(maxiter, zs, cs): output = [0] * len(zs) for i in range(len(zs)): n = 0 z = zs[i] c = cs[i] while abs(z) < 2 and n < maxiter: z = z * z + c n += 1 output[i] = n return output def calc_pure_python(desired_width, max_itertions): x_step = (float(x2 - x1)) / float(desired_width) y_step = (float(y2 - y1)) / float(desired_width) x, y = [], [] ycoord = y1 while ycoord < y2: y.append(ycoord) ycoord += y_step xcoord = x1 while xcoord < x2: x.append(xcoord) xcoord += x_step zs, cs = [], [] for ycoord in y: for xcoord in x: zs.append(complex(xcoord, ycoord)) cs.append(complex(c_real, c_imag)) print(f"Length of x: {len(x)}") print(f"Total elements: {len(zs)}") start_time = time.time() output = calculate_z_serial_purepython(max_itertions, zs, cs) end_time = time.time() secs = end_time - start_time print("calculate_z_serial_purepython took", secs, "seconds") assert sum(output) == 33219980 # # show img # output = np.array(output).reshape(desired_width, desired_width) # plt.imshow(output, cmap='gray') # plt.savefig("julia.png") if __name__ == "__main__": calc_pure_python(desired_width=1000, max_itertions=300)

这段代码运行完,可以得到图片

写一手漂亮的代码,走向极致的编程 一、代码运行时间分析

运行结果

Length of x: 1000 Total elements: 1000000 calculate_z_serial_purepython took 25.053941249847412 seconds 开始分析

这里,将通过各种方法来对这段代码的运行时间来进行分析

直接打印运行时间

在前面的代码中,我们可以看到有 start_time 和 end_time 两个变量,通过 print 两个变量的差值即可得到运行时间,但是,每次想要打印运行时间都得加那么几行代码就会很麻烦,此时我们可以通过使用修饰器来进行

from functools import wraps def timefn(fn): @wraps(fn) def measure_time(*args, **kwargs): start_time = time.time() result = fn(*args, **kwargs) end_time = time.time() print("@timefn:" + fn.__name__ + " took " + str(end_time - start_time), " seconds") return result return measure_time

然后对 calculate_z_serial_purepython 函数进行测试

@timefn def calculate_z_serial_purepython(maxiter, zs, cs): ...

运行后输出结果

Length of x: 1000 Total elements: 1000000 @timefn:calculate_z_serial_purepython took 26.64286208152771 seconds calculate_z_serial_purepython took 26.64286208152771 seconds

另外,也可以在命令行中输入

python -m timeit -n 5 -r 5 -s "import code" "code.calc_pure_python(desired_width=1000, max_itertions=300)"

其中 -n 5 表示循环次数, -r 5 表示重复次数,timeit 会对语句循环执行 n 次,并计算平均值作为一个结果,重复 r 次选出最好的结果。

5 loops, best of 5: 24.9 sec per loop UNIX tine 命令

由于电脑上没有 Linux 环境,于是使用 WSL 来进行

time -p python code.py 如果是 Linux 中进行,可能命令需改成 /usr/bin/time -p python code.py

输出结果

Length of x: 1000 Total elements: 1000000 @timefn:calculate_z_serial_purepython took 14.34933090209961 seconds calculate_z_serial_purepython took 14.350624322891235 seconds real 15.57 user 15.06 sys 0.40

其中 real 记录整体耗时, user 记录了 CPU 花在任务上的时间,sys 记录了内核函数耗费的时间

/usr/bin/time --verbose python code.py

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwyzxj.html