- 内容提要
- 前言
- 作者简介
- 封面简介
- 第1章 理解高性能 Python
- 第2章 通过性能分析找到瓶颈
- 2.1 高效地分析性能
- 2.2 Julia 集合的介绍
- 2.3 计算完整的 Julia 集合
- 2.4 计时的简单方法——打印和修饰
- 2.5 用 UNIX 的 time 命令进行简单的计时
- 2.6 使用 cProfile 模块
- 2.7 用 runsnakerun 对 cProfile 的输出进行可视化
- 2.8 用 line_profiler 进行逐行分析
- 2.9 用 memory_profiler 诊断内存的用量
- 2.10 用 heapy 调查堆上的对象
- 2.11 用 dowser 实时画出变量的实例
- 2.12 用 dis 模块检查 CPython 字节码
- 2.13 在优化期间进行单元测试保持代码的正确性
- 2.14 确保性能分析成功的策略
- 2.15 小结
- 第3章 列表和元组
- 第4章 字典和集合
- 第5章 迭代器和生成器
- 第6章 矩阵和矢量计算
- 第7章 编译成 C
- 第8章 并发
- 第9章 multiprocessing 模块
- 第10章 集群和工作队列
- 第11章 使用更少的 RAM
- 第12章 现场教训
2.3 计算完整的 Julia 集合
我们在本节分解Julia集合的生成代码。我们将在本章以各种方法分析它。如例2-1所示,在模块的一开始,我们导入time模块作为我们的第一种分析手段并定义一些坐标常量。
例2-1 定义空间坐标的全局常量
"""Julia set generator without optional PIL-based image drawing""" import time # area of complex space to investigate x1, x2, y1, y2 = -1.8, 1.8, -1.8, 1.8 c_real, c_imag = -0.62772, -.42193
为了生成图像,我们创建两列输入数据。第一列zs(复数z的坐标),第二列cs(复数的初始条件)。这两列数据都不会发生变化,且cs列表可以被优化成一个c常量。建立两列输入的理由是之后当我们在本章分析RAM使用情况时可以有一些看上去合理的数据来进行分析。
为了创建zs列表和cs列表,我们需要知道每个z的坐标。例2-2中,我们用xcoord和ycoord以及指定的x_step和y_step创建了这些坐标。这样一个冗长的设定方式有助于将代码移植到其他工具(如numpy)或Python环境上,因为它将一切都明确定义下来,方便除错。
例2-2 建立坐标列表作为计算函数的输入
def calc_pure_python(desired_width, max_iterations): """Create a list of complex coordinates (zs) and complex parameters (cs), build Julia set, and display""" x_step = (float(x2 - x1) / float(desired_width)) y_step = (float(y1 - y2) / float(desired_width)) x = [] y = [] ycoord = y2 while ycoord > y1: y.append(ycoord) ycoord += y_step xcoord = x1 while xcoord < x2: x.append(xcoord) xcoord += x_step # Build a list of coordinates and the initial condition for each cell. # Note that our initial condition is a constant and could easily be removed; # we use it to simulate a real-world scenario with several inputs to # our function. zs = [] cs = [] for ycoord in y: for xcoord in x: zs.append(complex(xcoord, ycoord)) cs.append(complex(c_real, c_imag)) print "Length of x:", len(x) print "Total elements:", len(zs) start_time = time.time() output = calculate_z_serial_purepython(max_iterations, zs, cs) end_time = time.time() secs = end_time - start_time print calculate_z_serial_purepython.func_name + " took", secs, "seconds" # This sum is expected for a 1000^2 grid with 300 iterations. # It catches minor errors we might introduce when we're # working on a fixed set of inputs. assert sum(output) == 33219980
创建了zs列表和cs列表后,我们输出一些有关列表长度的信息并用calculate_z_serial_purepython计算output列表。最后我们对output的内容求和并断言它符合预期的输出值。Ian靠它来确保本书此处不至于出现错误。
既然代码已经确定,我们只需要对所有计算出的值求和就可以验证函数是否如我们预期的那样工作。这叫完整性检查——当我们对计算代码进行修改时,很有必要进行这样的检查来确保我们没有破坏算法的正确性。理想情况下,我们会使用单元测试且对该问题的多个不同配置进行测试。
接下来,在例2-3中,我们定义calculate_z_serial_purepython函数,它扩展了我们之前讨论的算法。注意,我们同时还在函数开头定义了一个output列表,其长度跟zs和cs列表相同。你可能会奇怪为什么我们使用range而不是xrange——这里就先这样,在2.9节,我们会告诉你range有多浪费。
例2-3 我们的CPU密集型计算函数
def calculate_z_serial_purepython(maxiter, zs, cs): """Calculate output list using Julia update rule""" 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
现在我们可以在例2-4中调用整个计算逻辑。只要将它包入一个__main__检查块,我们就可以安全地为某些性能分析手段导入这个模块而不是直接开始计算。注意,将输出画成图像的方法我们这里没有显示出来。
例2-4 我们代码的main函数
if __name__ == "__main__": # Calculate the Julia set using a pure Python solution with # reasonable defaults for a laptop calc_pure_python(desired_width=1000, max_iterations=300)
一旦我们运行这段代码,就能够看到一些关于问题复杂度的输出:
# running the above produces: Length of x: 1000 Total elements: 1000000 calculate_z_serial_purepython took 12.3479790688 seconds
图2-1所示的伪灰阶图中,高对比的颜色变化给我们一个该函数运行快慢的直观感受。在此处的图2-3中,我们有一个线性的灰阶图:黑色是算得快的地方而白色是计算开销大的地方。通过对同一数据的两种表达,我们能够看到线性图上丢失了很多细节。有时,当我们调查一个函数的开销时,多种表达方式有助于我们查清问题。
图2-3 Julia集合的纯灰阶图
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论