Python 高手编程系列五百八十七:扩展硬件

当没有更多的非永久性内存可用时,系统开始使用硬盘来存储数据。这就是交换。
这涉及很多开销,性能会急剧下降。从用户的角度来看,在这个阶段,系统被认为是
僵死的。因此,扩展硬件以防止这种情况是非常重要的。
尽管在系统上有足够的内存很重要,但是确保应用程序不会表现太疯狂和吃太多内存
也很重要。例如,如果一个程序处理一个几百兆字节大小的大视频文件,那它不应该将视
频文件完全加载到内存,而是应该分块处理或使用磁盘流。
磁盘使用也很重要。如果 I/O 错误隐藏在试图在磁盘上重复写入的代码中,则整个分区可能
真的会减慢应用程序。此外,即使代码只尝试写一次,硬件和操作系统也可能尝试多次写入。
注意,扩展硬件(垂直扩展)有一些明显的限制。你无法在单个机架上安装无限量的
硬件。此外,高效的硬件是非常昂贵的(收益递减规律),因此这种方法也存在经济上的限
制。从这个角度来看,通过添加新的计算节点或工作节点(水平扩展)扩展系统总是更好
的。这允许你使用具有最佳性价比的商品软件扩展你的服务。
不幸的是,设计和维护高度可扩展的分布式系统既困难又昂贵。如果你的系统不能轻
松地水平扩展,或者更快更便宜地垂直扩展,那么最好这样做,而不是浪费时间和资源来
重新设计你的系统架构。记住,随着时间的推移,硬件总是趋向于更快、更便宜。许多产
品停留在这个最佳平衡点上,在这里扩展需求与提高硬件性能趋于一致。
编写速度测试
当开始优化工作时,重要的是使用类似于测试驱动开发的工作流,而不是持续地运行
一些手动测试。一个好的做法是在应用程序中提供一个测试模块,在该模块编写要优化的
调用序列。拥有此场景可帮助你在优化应用程序时跟踪进度。
你甚至可以在你设置的速度目标处写几个断言。为了防止速度回归,在代码优化后,
可以继续保留以下这些测试:

def test_speed():
… import time
… start = time.time()
… the_code()
… end = time.time() - start
… assert end < 10,
… “sorry this code should not take 10 seconds !”

查找瓶颈
可以通过以下方法找到应用程序的瓶颈。
• 分析 CPU 使用情况。
• 分析内存使用情况。
• 分析网络使用情况。
分析 CPU 使用情况
瓶颈的第一个来源是你的代码。标准库提供执行代码分析所需的所有工具。它们基于
确定性方法。
确定性分析器(deterministic profiler)通过在最底层添加定时器来测量在每个函数中花
费的时间。这引入了一点开销,但提供了一个查找哪里消耗时间的好办法。另一方面,统
计分析器(statistical profiler)对指令指针的使用进行采样,并且不对代码进行操作。后者
不太准确,但允许以全速运行目标程序。
有两种方法来分析代码。
• 宏观分析(Macro-profiling):当程序运行时,对整个程序进行分析,并生成统计数据。
• 微观分析(Micro-profiling):通过手动装置测量程序的精确部分。
宏观分析
通过在特殊模式下运行应用程序来完成宏观分析,在此模式下,会检测解释器并收集
有关代码使用的统计信息。Python 为此提供了几个工具。
• profile:这是一个纯 Python 实现。
• cProfile:这是一个 C 实现,提供与 profile 工具相同的接口,但具有较少的开销。
大多数 Python 程序员推荐选择 cProfile,因为它减少了开销。无论如何,如果你需
要以某种方式扩展分析器,那么 profile 可能会是一个更好的选择,因为它不使用 C 扩展。
这两个工具具有相同的接口和用法,因此我们将只使用其中一个来展示它们如何工作。
下面是一个带有 main 函数的 myapp.py 模块,我们将使用 cProfile 进行测试:
import time
def medium():
time.sleep(0.01)
def light():
time.sleep(0.001)
def heavy():
for i in range(100):
light()
medium()
medium()
time.sleep(2)
def main():
for i in range(2):
heavy()
if name == ‘main’:
main()
模块可以直接从命令提示符中调用,结果总结如下:
$ python3 -m cProfile myapp.py
1208 function calls in 8.243 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
2 0.001 0.000 8.243 4.121 myapp.py:13(heavy)
1 0.000 0.000 8.243 8.243 myapp.py:2()
1 0.000 0.000 8.243 8.243 myapp.py:21(main)
400 0.001 0.000 4.026 0.010 myapp.py:5(medium)
200 0.000 0.000 0.212 0.001 myapp.py:9(light)
1 0.000 0.000 8.243 8.243 {built-in method exec}
602 8.241 0.014 8.241 0.014 {built-in method sleep}
提供的统计信息是由分析器填充的统计对象的打印视图。也可以手动调用工具,如下所示:
import cProfile
from myapp import main
profiler = cProfile.Profile()
profiler.runcall(main)
profiler.print_stats()
1206 function calls in 8.243 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall file:lineno(function)
2 0.001 0.000 8.243 4.121 myapp.py:13(heavy)
1 0.000 0.000 8.243 8.243 myapp.py:21(main)
400 0.001 0.000 4.026 0.010 myapp.py:5(medium)
200 0.000 0.000 0.212 0.001 myapp.py:9(light)
602 8.241 0.014 8.241 0.014 {built-in method sleep}
统计信息还可以保存到文件中,然后由 pstats 模块读取。这个模块提供了一个类,
知道如何处理分析文件,并给出一些帮助,以便与它们调用,如下所示:
import pstats
import cProfile
from myapp import main
cProfile.run(‘main()’, ‘myapp.stats’)
stats = pstats.Stats(‘myapp.stats’)
stats.total_calls
1208
stats.sort_stats(‘time’).print_stats(3)
Mon Apr 4 21:44:36 2016 myapp.stats
1208 function calls in 8.243 seconds
Ordered by: internal time
List reduced from 8 to 3 due to restriction <3>
ncalls tottime percall cumtime percall file:lineno(function)
602 8.241 0.014 8.241 0.014 {built-in method sleep}
400 0.001 0.000 4.025 0.010 myapp.py:5(medium)
2 0.001 0.000 8.243 4.121 myapp.py:13(heavy)
从那里,你可以通过打印出每个函数的调用者和被调用者来浏览代码:
stats.print_callees(‘medium’)
Ordered by: internal time
List reduced from 8 to 1 due to restriction <‘medium’>
Function called…
ncalls tottime cumtime
myapp.py:5(medium) -> 400 4.025 4.025 {built-in method sleep}
stats.print_callees(‘light’)
Ordered by: internal time
List reduced from 8 to 1 due to restriction <‘light’>
Function called…
ncalls tottime cumtime
myapp.py:9(light) -> 200 0.212 0.212 {built-in method sleep}
能够将工作在不同的视图的输出进行排序,用于找到瓶颈。例如,请考虑以下场景:
• 当调用的数量真的很高并且占用全局时间的大部分时,函数或方法可能在循环中。
可以通过将该调用移动到不同的范围来进行优化,这样可以减少操作的数量。
• 当一个函数需要很长时间时,尽可能的使用缓存。
(https://github.com/jrfonseca/gprof2dot)可以将分析器数据转换为点图。你可以从 PyPI 上使
用 pip 下载这个简单的脚本,只要 Graphviz(参见 http://www.graphviz.org/)安装在你的
环境中,就可以使用它如下所示:
$ gprof2dot.py -f pstats myapp.stats | dot -Tpng -o output.png
gprof2dot 的优点是它是语言无关的。它不仅限于 Python 的 profile 或 cProfile
的输出,而且可以从多个其他分析文件中(如 Linux perf,xperf,gprof,Java HPROF 等)
读取数据。
宏观分析是一个检测函数的好方法,可以发现函数中的问题或者至少是它附近的问题。
当你找到问题的所在,你可以使用微观分析。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值