高效Python-2-1 剖析(Profiling 性能分析)

这篇具有很好参考价值的文章主要介绍了高效Python-2-1 剖析(Profiling 性能分析)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

2 从内置功能中获取最高性能

本章包括

  • 剖析代码以发现速度和内存瓶颈
  • 更有效地利用现有的Python数据结构
  • 了解Python分配典型数据结构的内存成本
  • 使用懒编程技术处理大量数据

有很多工具和库可以帮助我们编写更高效的Python。但是,在我们深入研究提高性能的所有外部选项之前,让我们先仔细看看如何编写在计算和IO性能方面都更高效的纯 Python代码。事实上,许多Python性能问题(当然不是全部)都可以通过更加注意Python的限制和能力来解决。

为了展示Python自身用于提高性能的工具,让我们将它们用于一个假设但现实的问题。假设您是一名数据工程师,负责准备对全球气候数据进行分析。这些数据将基于美国国家海洋和大气管理局(NOAA;http://mng.bz/ydge )的综合地表数据库。您的时间很紧,而且只能使用大部分标准Python。此外,由于预算限制,购买更强的处理能力也是不可能的。数据将在一个月后开始到达,您计划利用数据到达前的时间来提高代码性能。因此,您的任务就是找到需要优化的地方并提高其性能。

您要做的第一件事就是对现有的代码进行剖析,以便摄取数据。你知道现有的代码速度很慢,但在尝试优化之前,你需要找到瓶颈的经验证据。剖析之所以重要,是因为它能让您以严谨、系统的方式搜索代码中的瓶颈。最常见的替代方法--猜测,在这里尤其无效,因为许多减速点可能很不直观。

我们将了解纯Python提供了哪些开箱即用的功能来帮助我们开发性能更高的代码。首先,我们将使用几种剖析工具对代码进行剖析,以发现问题所在。然后,我们将重点关注Python的基本数据结构:列表、集合和字典。我们的目标是提高这些数据结构的效率,并以最佳方式为它们分配内存,以获得最佳性能。最后,我们将了解现代Python懒编程技术如何帮助我们提高数据管道的性能。

本章将主要讨论在没有外部库的情况下优化Python,但我们仍将使用一些外部工具来帮助我们优化性能和访问数据。我们将使用Snakeviz来可视化Python剖析的输出,并使用line_profiler来逐行剖析代码。最后,我们将使用requests库从互联网下载数据。

2.1对具有IO和计算工作负载的应用程序进行剖析

我们的第一个目标是从气象站下载数据,并获取该气象站某一年的最低温度。NOAA 网站上的数据有 CSV 文件,每个年份一个,然后每个站点一个。例如,文https://www.ncei.noaa.gov/data/global-hourly/access/2021/01494099999.csv 包含01494099999气象站2021年的所有条目。其中包括温度和气压等条目,每天可能会记录多次。

让我们开发一个脚本,下载一组站点在某一年份间隔内的数据。下载相关数据后,我们将得到每个站点的最低气温。

2.1.1 下载数据并计算最低气温

我们的脚本将有一个简单的命令行界面,通过该界面传递站点列表和感兴趣的年份间隔。

执行:

# 获取站点01044099999和02293099999 2021年的数据
$ python load.py 01044099999,02293099999 2021-2021
{'01044099999': -10.0, '02293099999': -27.6}

源码

import collections
import csv
import sys

import requests

stations = sys.argv[1].split(",") #站点用逗号分割
years = [int(year) for year in sys.argv[2].split("-")] #年份用区间表示
start_year = years[0]
end_year = years[1]

TEMPLATE_URL = "https://www.ncei.noaa.gov/data/global-hourly/access/{year}/{station}.csv"
TEMPLATE_FILE = "station_{station}_{year}.csv"

def download_data(station, year):
    my_url = TEMPLATE_URL.format(station=station, year=year)
    req = requests.get(my_url)
    if req.status_code != 200:
        return  # not found
    w = open(TEMPLATE_FILE.format(station=station, year=year), "wt")
    w.write(req.text)
    w.close()


def download_all_data(stations, start_year, end_year):
    for station in stations:
        for year in range(start_year, end_year + 1):
            download_data(station, year)


# 用pandas更佳
def get_file_temperatures(file_name):
    with open(file_name, "rt") as f:
        reader = csv.reader(f)
        header = next(reader)
        for row in reader:
            station = row[header.index("STATION")]
            tmp = row[header.index("TMP")]
            temperature, status = tmp.split(",")
            if status != "1":
                continue
            temperature = int(temperature) / 10
            yield temperature


def get_all_temperatures(stations, start_year, end_year):
    temperatures = collections.defaultdict(list)
    for station in stations:
        for year in range(start_year, end_year + 1):
            for temperature in get_file_temperatures(TEMPLATE_FILE.format(station=station, year=year)):
                temperatures[station].append(temperature)
    return temperatures


def get_min_temperatures(all_temperatures):
    return {station: min(temperatures) for station, temperatures in all_temperatures.items()}


download_all_data(stations, start_year, end_year)
all_temperatures = get_all_temperatures(stations, start_year, end_year)
min_temperatures = get_min_temperatures(all_temperatures)
print(min_temperatures)

现在,真正的乐趣开始了。我们的目标是在许多年里不断从许多站点下载大量数据。为了处理如此大量的数据,我们希望尽可能提高代码的效率。提高代码效率的第一步是有条理地全面剖析代码,找出拖慢代码运行的瓶颈。为此,我们将使用Python内置的剖析机制。

2.1.2 Python 内置剖析模块

我们要确保代码尽可能高效,首先要做的就是找到代码中存在的瓶颈。我们首先要做的就是对代码进行剖析,检查每个函数的耗时。为此,我们通过cProfile模块运行代码。请确保不要使用profile模块,因为它的速度要慢很多;只有当您自己开发剖析工具时,它才有用。

我们需要的是按累计时间排序的配置文件统计数据。使用该模块的最简单方法是在模块调用中将我们的脚本传递给profiler,如下所示:

$ python -m cProfile -s cumulative load.py 01044099999,02293099999 2021-2021 > profile.txt
$ cat profile.txt 
{'01044099999': -10.0, '02293099999': -27.6}
         387321 function calls (381489 primitive calls) in 16.216 seconds		

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    174/1    0.000    0.000   16.216   16.216 {built-in method builtins.exec}
        1    0.000    0.000   16.216   16.216 load.py:1(<module>)
        1    0.000    0.000   16.013   16.013 load.py:25(download_all_data)
        2    0.001    0.000   16.013    8.006 load.py:15(download_data)
        2    0.000    0.000   15.973    7.986 api.py:62(get)
        2    0.000    0.000   15.973    7.986 api.py:14(request)
        2    0.000    0.000   15.972    7.986 sessions.py:500(request)
[...]       
        1    0.000    0.000    0.000    0.000 socks.py:78(SOCKS5AuthError)

在许多情况下I/O有可能在所需时间方面占据主导地位。在我们的例子中,既有网络I/O(从NOAA获取数据),也有磁盘I/O(将数据写入磁盘)。由于网络成本取决于沿途的许多连接点,因此即使在不同的运行中,网络成本也会有很大差异。由于网络成本通常是最大的时间损失,因此我们要尽量减少网络成本。

2.1.3 使用本地缓存减少网络使用量

为了减少网络通信,让我们在首次下载文件时保存一份副本,以备将来使用。我们将建立一个本地数据缓存。除了函数download_all_data外,我们将使用与前面相同的代码:

def download_all_data(stations, start_year, end_year):
    for station in stations:
        for year in range(start_year, end_year + 1):
            if not os.path.exists(TEMPLATE_FILE.format(station=station, year=year)):
                download_data(station, year)

执行结果:

$ python -m cProfile -s cumulative load_cache.py 01044099999,02293099999 2021-2021 > profile_cache.txt
$ head profile_cache.txt 
{'01044099999': -10.0, '02293099999': -27.6}
         316570 function calls (310825 primitive calls) in 0.187 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    172/1    0.000    0.000    0.187    0.187 {built-in method builtins.exec}
        1    0.000    0.000    0.187    0.187 load_cache.py:1(<module>)
       16    0.000    0.000    0.169    0.011 __init__.py:1(<module>)
        1    0.007    0.007    0.103    0.103 load_cache.py:51(get_all_temperatures)

虽然运行时间缩短了一个数量级,但IO仍然耗时最长。现在,不是网络问题,而是磁盘访问问题。这主要是由于计算量过低造成的。

缓存管理也可能存在问题,而且是常见错误的根源。在我们的示例中,文件从未随时间发生变化,但缓存的许多用例中,源文件可能会发生变化。在这种情况下,缓存管理代码需要认识到这个问题。我们将在本书的其他部分再次讨论缓存问题。

2.2 剖析代码以检测性能瓶颈

在这里,我们将研究CPU耗时最长的代码。我们将使用NOAA数据库中的所有站点计算它们之间的距离,这是一个复杂度为n2的问题。

由于我们要比较所有站点之间的距离,因此复杂度为 n2。
前面的代码需要很长时间才能运行。同时也会占用大量内存。如果您有内存问题,请限制要处理的站点数量。现在,让我们使用 Python 的剖析基础结构来看看大部分时间都花在哪里了。

2.2.1 可视化剖析信息

我们再次查找延迟执行的代码片段。但为了更好地检查跟踪,我们将使用外部可视化工具SnakeViz(https://jiffyclub.github.io/snakeviz/)。

# pip install snakeviz
$ python -m cProfile -o distance_cache.prof distance_cache.py

注意Python提供了pstats模块来分析写入磁盘的跟踪信息。您可以执行 python -m pstats distance_cache.prof,这将启动一个命令行界面来分析我们脚本的代价。有关该模块的更多信息,请参阅 Python 文档或第 5 章的剖析部分。

为了分析这些信息,我们将使用网络可视化工具SnakeViz。您只需执行snakeviz distance_cache.prof。这将启动一个交互式浏览器窗口(图 2.1 显示了一个截图)。

高效Python-2-1 剖析(Profiling 性能分析)

2.2 行剖析

我们将使用https://github.com/pyutils/line_profiler上。使用行剖析器非常简单:只需在 get_distance 中添加注解即可:

@profile
def get_distance(p1, p2):

这是因为我们将使用line_profiler软件包中的便捷脚本kernprof来:

kernprof -l lprofile_distance_cache.py

行剖析器所需的工具会大大降低代码的运行速度,慢上几个数量级。如果中断运行,仍会有跟踪记录。剖析器运行结束后,可以使用以下命令查看结果:

$ python -m line_profiler lprofile_distance_cache.py.lprof
Timer unit: 1e-06 s

Total time: 19.194 s
File: lprofile_distance_cache.py
Function: get_distance at line 16

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    16                                           @profile
    17                                           def get_distance(p1, p2):
    18   6285284    1038835.3      0.2      5.4      lat1, lon1 = p1
    19   6285284     942398.6      0.1      4.9      lat2, lon2 = p2
    20                                           
    21   6285284    1425843.5      0.2      7.4      lat_dist = math.radians(lat2 - lat1)
    22   6285284    1342482.5      0.2      7.0      lon_dist = math.radians(lon2 - lon1)
    23   6285284     611137.0      0.1      3.2      a = (
    24   6285284    2646991.4      0.4     13.8          math.sin(lat_dist / 2) * math.sin(lat_dist / 2) +
    25  12570568    3500465.6      0.3     18.2          math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
    26  12570568    2468080.8      0.2     12.9          math.sin(lon_dist / 2) * math.sin(lon_dist / 2)
    27                                               )
    28   6285284    2877574.5      0.5     15.0      c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    29   6285284     725911.4      0.1      3.8      earth_radius = 6371
    30   6285284     950183.7      0.2      5.0      dist = earth_radius * c
    31                                           
    32   6285284     664096.4      0.1      3.5      return dist

参考资料

  • 软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
  • 本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
  • python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
  • Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html

2.2.3 Profiling小结

正如我们所看到的,作为第一种方法,内置剖析器总体上是一个很大的帮助;它也比行剖析快得多。但是行剖析的信息量要大得多,这主要是因为内置剖析不提供函数内部的细分。相反,Python的剖析只提供每个函数的累计值,并显示花费在子调用上的时间。在特定情况下,可以知道一个子调用是否属于另一个函数,但一般来说,这是不可能的。剖析的总体策略需要考虑到所有这些因素。

我们在这里使用的策略是一种普遍合理的方法:首先,尝试内置的cProfile,因为它速度快,而且能提供一些高级信息。如果这还不够,可以使用行剖析,它的信息量更大,但速度也更慢。请记住,在这里我们主要关注的是找到瓶颈;后面的章节将提供优化代码的方法。有时,仅仅改变现有解决方案的部分内容是不够的,还需要进行总体架构重构;我们也会在适当的时候讨论这个问题。

timeit可能是新手最常用的代码剖析方法,你可以在互联网上找到大量使用 timeit 模块的示例。使用timeit模块最简单的方法是使用IPython或Jupyter Notebook,因为这些系统能让timeit非常精简。例如,在IPython中,只需将%timeit魔法添加到你想要剖析的内容中即可:

In [1]: %timeit list(range(1000000))
18.5 ms ± 37.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [2]: %timeit range(1000000)
82.1 ns ± 0.721 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

这将为您提供正在剖析的函数的多次运行时间。这个魔法将决定运行多少次并报告基本统计信息。在前面的代码段中,你可以看到range(1000000)和list(range(1000000))的区别。在这个具体案例中,timeit显示,range的懒惰版本比急切版本快两个数量级。

你可以在timeit模块的文档中找到更多细节,但在大多数情况下,使用IPython的%timeit功能就足够了。我们鼓励你使用IPython及其魔法,但在本书的其他大部分内容中,我们将使用标准解释器。有关%timeit魔法的更多信息,请访问:https://ipython.readthedocs.io/en/stable/interactive/magics.html。文章来源地址https://www.toymoban.com/news/detail-622415.html

到了这里,关于高效Python-2-1 剖析(Profiling 性能分析)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python 进阶指南(编程轻松进阶):十三、性能测量和大 O 算法分析

    原文:http://inventwithpython.com/beyond/chapter13.html 对于大多数小程序来说,性能并不那么重要。我们可能会花一个小时编写一个脚本来自动执行一个只需要几秒钟就能运行的任务。即使需要更长的时间,当我们端着一杯咖啡回到办公桌时,这个项目也可能已经完成了。 有时候花时

    2023年04月09日
    浏览(34)
  • 【OpenCV-PyQt5-PyGame-imutils】探索Python中的图像和视频捕获:性能分析与选择指南

    随着计算机视觉和多媒体应用的不断发展,图像和视频捕获变得越来越重要。在Python中,有多种库和工具可供选择,用于打开摄像头、捕获图像、以及处理视频流。本文旨在为读者提供对这些捕获方法的全面了解,并介绍如何计算平均帧率以评估性能。 解释器环境创建方式基

    2024年02月07日
    浏览(33)
  • 1.UnityProfiler性能分析提升性能

    1.main thread 主线程 业务逻辑都在这里,我们调用Unity API都在这里;例如设置transform位置,main thread里面处理 2.render thread,渲染线程,负责渲染图像、执行渲染循环、处理GPU命令、帧同步。 3.  这个则表示当前负载最多可以绘制多少次(当前帧数) 4.Batches :批次,绘制场景,

    2024年01月25日
    浏览(48)
  • 【kafka性能测试脚本详解、性能测试、性能分析与性能调优】

    Apache Kafka 官方提供了两个客户端性能测试脚本,它们的存放位置如下: 生产者性能测试脚本:$KAFKA_HOME/bin/kafka-producer-perf-test.sh 消费者性能测试脚本:$KAFKA_HOME/bin/kafka-consumer-perf-test.sh kafka-producer-perf-test.sh 支持测试的性能指标包括:吞吐量(throughput)、最大时延(max-latenc

    2024年02月04日
    浏览(54)
  • 一文1800字解读性能指标与性能分析

    1、系统指标:与⽤户场景与需求直接相关的指标 2、服务器资源指标:硬件服务器的资源使⽤情况的指标 3、JAVA应⽤ : JAVA应⽤程序在运⾏时的各项指标 4、数据库:数据库服务器运⾏时需要监控的指标 5、压测机资源指标:测试机在模拟⽤户负载时的资源使⽤情况 ⼀般情况下,测试

    2024年02月08日
    浏览(45)
  • 辅助性能优化——长安链性能分析工具原理及用法

    如何提升区块链系统性能是很多开发者都会关注的事,但是有些对区块链并非十分熟悉的开发者可能会感到没有头绪。长安链提供了性能分析工具帮助开发者梳理系统耗时,优化系统性能。下面对长安链性能分析工具原理及使用进行介绍。 time_counter.sh是长安链性能分析工具,

    2024年02月13日
    浏览(40)
  • Linux性能瓶颈分析之TOP指标分析

    1.查看cpu基础信息 2.查看cpu详细信息 3.统计cpu信息 如图所示: us :表示 CPU 在用户态运行的时间百分比,通常用户态 CPU 高表示有应用程序比较繁忙。 sy :表示 CPU 在内核态运行的时间百分比,通常内核态 CPU 越低越好,否则表示系统存在某些瓶颈。 ni :表示用 nice 修正进程

    2024年02月11日
    浏览(60)
  • Mysql的SQL性能分析【借助EXPLAIN分析】

    要说sql有问题,需要拿出证据,因此需要性能分析 Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(它认为最优的数据检索方式,不见得是DBA认为是最优的,这部分最耗费时间,

    2024年02月12日
    浏览(54)
  • 排序算法性能分析

    目录 实现插入排序、冒泡排序、选择排序、合并排序、快速排序算法(从小到大) ①插入排序 ②冒泡排序 ③选择排序 ⑥快速排序 五种排序 现在有10亿的数据(每个数据四个字节),请快速挑选出最大的十个数,并在小规模数据上验证算法的正确性。 方法一:规模为10的插

    2024年02月13日
    浏览(41)
  • 磁盘性能分析

    %iowait并不能反应磁盘瓶颈 iowait实际测量的是cpu时间: %iowait = (cpu idle time)/(all cpu time) 这个文章说明:高速cpu会造成很高的iowait值,但这并不代表磁盘是系统的瓶颈。唯一能说明磁盘是系统瓶颈的方法,就是很高的read/write时间,一般来说超过20ms,就代表了不太正常的磁盘性能

    2024年02月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包