python 性能优化

news2024/12/23 16:38:21

文章目录

  • 性能测试
    • 运行速度
    • 内存消耗
  • 并行加速
    • 分布式
    • 并行
      • 多线程
      • 多进程
      • 框架
  • 即时编译
    • njit
      • case1 计算熵
      • case2 找到最大概率类别
      • case3 计算两两准确率
  • GPU
  • 使用工具

关于程序优化的第一个准则是“不要优化”,第二个准则是“不要优化那些无关紧要的部分”。

性能测试

性能测试是做性能调优前最重要的工作。比如 python 本地程序一定要有时间和调用次数的打点,我们才能确定是 io 瓶颈还是计算瓶颈,还是算法复杂度自身有问题。再比如 mr 任务的话,要合理利用平台功能,监控 cpu 利用率和 memory 利用率,cpu 利用率低,可以降低资源申请或者提高异步;如果 memory 利用率低,通常就是降低资源申请。往往降低资源利用率也可以侧面的提供更高的并发可能性。

运行速度

# opti exe-time
# 参考:https://zhuanlan.zhihu.com/p/53760922
import cProfile, pstats, io, sys
from decorator import decorator
@decorator
def profile_time(func, *args, **kw):
    # pre
    pr = cProfile.Profile()
    pr.enable()

    # main func
    result = func(*args, **kw)

    # post
    pr.disable()
    s = io.StringIO()

	# tottime,指的是函数本身的运行时间,扣除了子函数的运行时间
    # cumtime,指的是函数的累计运行时间,包含了子函数的运行时间
    sortby = "cumtime"  # 仅适用于 3.6, 3.7 把这里改成常量了
    ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
    ps.print_stats()
    sys.stderr.write("{}\n".format(s.getvalue()))
    pr.dump_stats("pipeline.prof")
    return result

上面这个装饰器直接装饰到需要计时的函数上就好了。上面这个装饰器还是顺便在当前路径生成一个 profile 文件,需要的话,调用该工具:flameprof pipeline.prof > pipeline.svg,就可以在浏览器中打开火焰图可视化了。

内存消耗

这个可以用 tracemalloc 工具,使用非常容易上手,因此这里不展开篇幅介绍了。

并行加速

分布式

这个我比较熟悉也是也借用的比较多的工具就是mapreduce 了,这里也不过多做介绍了。不过最近在整理 mapreduce 的相关零碎知识点,这里先挖个坑,后面会单独整理一下工作中遇到 mapreduce 的技巧和坑。
需要注意的是,因为 map->reduce阶段框架已经做了 shuffle+sort,因此,reducer 可以注意要尽可能的使用流式计算的逻辑。全局状态尽可能的少存,及时要存,我们也尽可能的使用流式算法。可以参考《互联网大规模数据挖掘与分布式处理》这本书。

并行

可以参考这一本小书:https://python-parallel-programmning-cookbook.readthedocs.io/zh_CN/latest/index.html
在这里插入图片描述

多线程

还有一点要注意的是,线程不是专门用来优化性能的。 一个CPU依赖型程序可能会使用线程来管理一个图形用户界面、一个网络连接或其他服务。 这时候,GIL会产生一些问题,因为如果一个线程长期持有GIL的话会导致其他非CPU型线程一直等待。 事实上,一个写的不好的C语言扩展会导致这个问题更加严重, 尽管代码的计算部分会比之前运行的更快些。

通过使用一个技巧利用进程池解决了GIL的问题。 当一个线程想要执行CPU密集型工作时,会将任务发给进程池。 然后进程池会在另外一个进程中启动一个单独的Python解释器来工作。 当线程等待结果的时候会释放GIL。 并且,由于计算任务在单独解释器中执行,那么就不会受限于GIL了。 在一个多核系统上面,你会发现这个技术可以让你很好的利用多CPU的优势。

其实这里我叫多线程,一般我只会用到两个线程。一个处理 io 秘籍的部分,一个处理 cpu 密集的部分。比如,数据处理任务经常遇到的场景是读取数据,然后进行处理,常为流式计算(比如特征转换)或者批式(比如统计全局信息)。那么我们就可以批量读取,然后批量计算,并且这两个之间可以异步。
在这里插入图片描述
上图展示的是 io 比 cpu 处理时间长的场景,如果 cpu 处理时间更长,那么还可以对于 cpu 密集部分进行并发计算。(注意,调度起 cpu 是一个线程,并发是使用多个进程)。
在这里插入图片描述

多进程

并发一般使用多进程的方式是 python 中 GIL 的存在。有很多方式可以实现多进程,我们这里只给出一个我常用的方式:

import concurrent
import concurrent.futures

input_queue = [......]
def mapper_concurrent_op(input_data):
    // ....
    return output_data

executor = concurrent.futures.ProcessPoolExecutor(max_workers=4)
result_iter = executor.map(mapper_concurrent_op, input_queue)
result_list = list(result_iter) # input_queue 处理后的结果

对于使用 ProcessPoolExecutor 时,这个方法会将 iterables 分割任务块并作为独立的任务并提交到执行池中。这些块的大概数量可以由 chunksize 指定正整数设置。 对很长的迭代器来说,使用大的 chunksize 值比默认值 1 能显著地提高性能。 chunksize 对 ThreadPoolExecutor 没有效果。参考python官方文档和Python并行编程 中文版

框架

这里可以给出一个现实中可行的框架设计。对于这种【读入数据】=>【处理】=>【输出】的处理流程,可以结合上面的 io+cpu 异步和并发计算密集部分的原理,给出下面的抽象。大致的业务函数只有标灰的 5 处。

  1. 输入:定义如何生成待处理的数据
  2. 处理:定义如何把输入加工为输出
  3. 输出有三部分:(1)每个数据如何输出(2)如何更新全局状态(3)全局状态如何输出。
    在这里插入图片描述
    代码实现我这里先不给出,读者可以自己尝试一下如何实现,其实并不复杂。

即时编译

这个我常用的工具是 numba。

njit

一般想想要发挥比较好的性能提升的话,尽量都使用 nopython 模式。我们展开个例子讲一下。

case1 计算熵

先给出计算熵的例子,先用 numpy 实现一个常规版本,这个版本其实日常用不追求性能完全足够了。

def entropy(data):
    sum_ = np.sum(data)
    probs = data / sum_
    log_probs = np.log2(probs)
    ent = -1 * np.sum(probs * log_probs)
    return ent

a = np.array(np.random.randint(0, 100, (500,)), np.float64)
%timeit entropy(a)  # 19.5 µs ± 181 ns

下面是 njit 编译的版本,去掉编译时间,可以看到时间明显变短

@nb.njit
def entropy_fast(data):
    #sum_ = np.sum(data)
    sum_ = 0
    for i in data:
        sum_ += i
    # probs = []
    # log_probs = []
    ent = 0
    for i in data:
        p = i/sum_ + 1e-5
        # probs.append(p)
        # log_probs.append(math.log2(p))
        ent += p * np.log2(p)
    # ent = -1 * np.sum(probs * log_probs)
    return ent * -1
entropy_fast(a) # 触发编译
%timeit entropy_fast(a)  # 4.18 µs ± 26.2 ns

我们也可以使用手动签名的 njit,如这样:@nb.njit("float64(float64[:])"),这样会加快编译速度,但是运行速度几乎不变。

case2 找到最大概率类别

a = np.array(np.random.randint(0, 1100, (1500,)), np.str)
b = np.array(np.random.randint(0, 1100, (1500,)), np.float64)
def get_max_ratio(keys, values):
    idx = np.argmax(values)
    sum_ = np.sum(values)
    return keys[idx], values[idx] / sum_ 

%timeit get_max_ratio(a,b) # 9.17 µs ± 452 ns

这个有一个问题,就是貌似 numba 不接受 str 类型的参数,一般可以通过外部封装一层的方式解决。

def get_max_ratio_fast(k,v):
    idx, ratio = get_max_ratio_fast_(v)
    return k[idx], ratio

def get_max_ratio_fast(k,v):
    idx, ratio = get_max_ratio_fast_(v)
    return k[idx], ratio
    
@nb.njit("Tuple((int64,float64))(float64[:])")
def get_max_ratio_fast_(values:list):
    max_idx = 0
    max_value = 0
    sum_ = 0
    for i, v in enumerate(values):
        if v > max_value:
            max_idx = i
            max_value = v
        sum_ += v
    return max_idx, max_value / sum_

%timeit get_max_ratio_fast(b) #2.04 µs ± 96.9 ns

可以参考看看这个文章:https://zhuanlan.zhihu.com/p/434078992

case3 计算两两准确率

前面两个场景都是 numpy 自身优化已经做得很不错了,使用 numba 有收益,但是除非在瓶颈时否则收益不大。这也是前面重点强调的: 第一个准则是“不要优化”,第二个准则是“不要优化那些无关紧要的部分” 的意思。
但是有一些场景,用 numpy 不好实现,或者说实现起来比较麻烦,那么此时我们就可以试试 numba 了,反正已经麻烦了,不如一步到位。
比如我这里举一个例子,也是工作中常常遇到的真实场景。比如我们有一组数据,我们要计算他们两两之间的关系,通常是类似偏序关系的指标计算,或者有向图相关的计算。我们就确定一个明确的案例,计算一组带 label 数据两两之间的准确率。因为这个问题需要双层循环,所以,numpy 实现起来还比较麻烦。(其实可以利用 numpy 的广播机制,不过可能很多同学想不到,双层循环式最直观的想法了)。

下面给出一个基础版本,应该很好理解。

def pair_acc(label:list, score:list) -> float:
    assert len(label) == len(score)
    length = len(label)
    right = wrong = 0
    for i in range(length):
        for j in range(i+1, length):
            if label[i] == label[j]:
                continue
            if label[i] > label[j] and score[i] > score[j] or\
               label[i] < label[j] and score[i] < score[j]:
                right += 1
            else:
                wrong += 1

    return right / (right + wrong)

我们再给出一个从逻辑上简单优化的版本,大致思路是,两个数据 label 如果一致不需要比较,因此可以直接跳过(也就是上面的 continue 部分)。其实很有很多优化点,不过这里只是做一个好一点的baseline,不做过多探讨。

# 预排序加速版本
def pair_acc2(label, score):
    assert len(label) == len(score)
    length = len(label)
    zip_list = list(zip(label, score))
    zip_list.sort(key=lambda x:x[0])
    label_, score_ = zip(*zip_list)
    i = j = 0
    right = error = 0

    while i < length:
        j = i
        while j < length and label_[j] == label_[i]:
                j += 1
        while j < length:
            if score_[j] > score_[i]:
                right += 1
            else:
                error += 1
            j += 1
        i += 1
    acc =  right / (right + error)
    return acc

然后就是给出 numba 版本,其实这种已经用循环方式写好的计算逻辑,特别容易转为 numba。我们直接看下代码,可以发现基本上就是 python 原始代码加了装饰器就可以。另外同样为了保证排序,这一步也是在 numba 版本函数外部封装了一层做到的。

# 加速2:jit编译
@nb.jit(nopython=True)
def pair_acc_fast_for_sorted(label_, score_):
    #assert len(label) == len(score)
    length = len(label_)
    i = j = 0
    right = error = 0

    while i < length:
        j = i
        while j < length and label_[j] == label_[i]:
                j += 1
        while j < length:
            if score_[j] > score_[i]:
                right += 1
            else:
                error += 1
            j += 1
        i += 1

    return right / (right + error)

# pair_acc_fast_for_sorted的外围函数,执行预排序的功能
def pair_acc_fast(label, score):
    zip_list = list(zip(label, score))
    zip_list.sort(key=lambda x:x[0])
    label_, score_ = zip(*zip_list)
    label_ = np.array(list(label_))
    score_ = np.array(list(score_))
    acc =  pair_acc_fast_for_sorted(label_, score_)
    return acc

在这里插入图片描述

在这里插入图片描述

GPU

【TODO】

使用工具

  1. 对于加载一个大文件内部转换为 dict 的场景可以转为使用 leveldb。
  2. regex 可以作为内置 re 包的无缝高性能替代。
  3. 大量的 json 解析和 dump 工作时可以使用第三方高效 json 包,ujson 是一个可以的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4种FPGA时钟分频 【附源码】:1.偶数分频;2.奇数分频(占空比50%);3.奇数分频(任意无占空比);4.小数分频;

题目来源于牛客网&#xff0c;完整工程源码&#xff1a;https://github.com/ningbo99128/verilog 目录 VL37 偶数分频 VL40 奇数分频&#xff08;占空比50%&#xff09; VL42 奇数分频&#xff08;任意无占空比&#xff09; VL41 任意小数分频 VL37 偶数分频 题目介绍 请…

https的相关知识,为什么https更加安全,为什么要对称与非对称加密,非对称加密的算法

目录 https相比于http更加安全&#xff0c;三个优势&#xff1a; 下面是一些必须知道的问题 1.对称加密与非对称加密&#xff1a; 2.对称加密的密钥SK如何产生和传输&#xff1a; 3.https有两套非对称加密 4.https的哈希一共用于两个地方 5.https的整个流程 https相比于ht…

ElasticSearch数据实时性原理分析与持久化

问题复现 现在有这么一种业务场景&#xff0c;需要将海量的数据通过Hive进行数据清洗并统计&#xff0c;最后落库到ES中&#xff0c;因为需要支持大数据量的分词&#xff0c;模糊搜索&#xff0c;所以考虑用ES而不直接放到Mysql中&#xff0c;前端需要直接对数据进行交互&…

Redis的自增也能实现滑动窗口限流?

文章目录限流核心原理以及代码基于Spring切面实现的注解版本限流是大家开发之路上一定会遇到的需求。比如&#xff1a;限制一定时间内&#xff0c;接口请求请求频率&#xff1b;一定时间内用户发言、评论次数等等&#xff0c;类似于滑动窗口算法。这里分享一份拿来即用的代码&a…

buildroot构建hisi平台根文件系统和工具链

buildroot构建hisi平台根文件系统和工具链 前面使用了arm-hisiv300-linux 工具链来作为Buildroot的外部工具链进行编译&#xff0c;然后遇到了很多编译问题。 https://blog.csdn.net/duapple/article/details/128516133?spm1001.2014.3001.5501 这里不使用hisi的工具链&…

Seata简介

小结&#xff1a; nacos 【name server】&#xff1a;注册中心&#xff0c;解决服务的注册与发现 nacos【config】&#xff1a;配置中心&#xff0c;微服务配置文件的中心化管理&#xff0c;同时配置信息的动态刷新 Ribbon&#xff1a;客户端负载均衡器&#xff0c;解决微服务集…

C++之引用类型,深浅拷贝构造

引用类型&#xff1a;给内存段取别名。 int m 10; //引用&#xff0c;给内存段取别名&#xff0c;所以需要给他一段内存段&#xff0c;而不只是声明。 int& n m;//不是赋值的意思&#xff0c;是别名的意思 想要在被调函数中修改主调函数中定义的变量的值时&#xff0c;…

小程序用户头像昵称获取规则调整与之对应调式策略、新API接口的bug

目录 调整时间 调整背景 调整说明 开发者与之对应的debug策略 1.button里面包含一个image&#xff0c;这种包含关系 2.然后我们可以看到官方给出的是用button组件中的open-type属性并且给到了一个chooseAvatar值&#xff01; 3.我们会发现光放给我们了一个“配置好的”命…

CSAPP Cache Lab

CSAPP Cache Lab 本实验将帮助您了解缓存存储器对 C 语言性能的影响程式。实验室由两部分组成。 在第一部分中&#xff0c;您将编写一个小的 C 程序&#xff08;大约 200-300 行&#xff09;模拟高速缓存的行为。 在第二部分中&#xff0c;您将优化一个小型矩阵转置函数&#…

NoMachine出现 The session negotiation failed的解决方案及踩坑总结

问题情况&#xff1a;我A电脑输入用户名和密码可以远程B电脑&#xff0c;B电脑输入用户名密码就是登录不上A电脑。 B电脑上密码是用的账户密码&#xff08;就是图标是一把钥匙的那个&#xff09;。 A电脑上的密码是用的PIN密码&#xff08;Win11系统推荐的那个&#xff09;。 通…

ArcGIS基础实验操作100例--实验37线要素生成规则或随机采样点

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验37 线要素生成规则或随机采样点 目录 一、实验背景 二、实验数据 三、实验步骤 &…

小程序安全设置的经验分享

一、小程序框架概述 在第一部分小程序框架概述中,将介绍小程序抽象框架、小程序调用框架和小程序初始化流程。下面让大白来逐一介绍。 1、小程序抽象框架 1.1视图层 包含WXML、WXSS和页面视图组件。 WXML是一种类似XML格式的语言,支持数据绑定、条件渲染、列表渲染、自定…

零入门容器云网络-10:基于golang编程netlink包方式操作tun设备

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…

【Linux】静态库和共享库

目录 库是什么 静态库和共享库 库的链接 优缺点 查看使用的库 制作库 制作静态库 静态库的使用 制作共享库 共享库的使用 静态库和共享库的区别 库是什么 库就是预先编译好的方法的集合 .h中是库函数的声明&#xff0c;库函数的实现在库中&#xff0c;如&#xff…

ChatGPT上线了!我在2023年1月2日这一天用上了它!百问百答!我只能说(真NB)算法工程师可以不用百度/Google了!

目录:问答结果1、你有什么nlp算法&#xff1f;2、平台终端3、如何训练深度学习模型&#xff1f;4、如何压缩nlp模型&#xff1f;5、bert模型有哪些用途&#xff1f;6、你知道汽车座舱吗&#xff1f;7、知识图谱有什么用途&#xff1f;8、能给一个构建知识图谱的案例吗&#xff…

S32K144—基于MBD的BLDC六步换相算法

可以简单分为六个功能区域&#xff1a; 1、全局变量 全局变量的定义是建模过程中遇到的第一个难点&#xff0c;因为它涉及到我们软件开发中最基础的东西——数据类型定义&#xff08;Data Types Definition&#xff09;。 在 Simulink 中可以通过 Bus Editor 构建自定义数据类…

【 shell 编程 】第5篇 文本编辑三剑客

文本编辑三剑客 文章目录文本编辑三剑客一、正则表达式1.基本正则表达式元字符2.拓展正则表达式元字符二、grep1.grep2.egrep3.fgrep三、sed四、awk一、正则表达式 1、简介&#xff1a;正则表达式是对字符串&#xff08;包括普通字符&#xff08;例如&#xff0c;a 到 z 之间的…

点云算法-提取kitti路面点云

目录 一、ransac原理 二、ransac 地面分割原理 三、ransac常见应用 四、代码 五、截图 六、总结 一、ransac原理 RANSAC是“random sample consensus&#xff08;随机抽样一致&#xff09;”的缩写。它可以从一组包含“局外点”的观测数据集中&#xff0c;通过迭代方式估…

【408篇】C语言笔记-第二十一章(汇编语言)

文章目录第一节&#xff1a;汇编指令格式讲解1. 汇编指令格式2. 生成汇编方法第二节&#xff1a;汇编常用指令讲解1. 相关寄存器2. 常用指令3. 条件码第三节&#xff1a;各种变量赋值汇编实战1. 各种变量赋值汇编实战解析第四节&#xff1a;选择循环汇编实战1. 选择循环汇编实战…

基于ssm+mysql+jsp实现歇后语管理系统

基于ssmmysqljsp实现歇后语管理系统一、系统介绍二、系统展示1.歇后语大全2.歇后语排行榜3.歇后语管理4.用户管理三、其它系统四、获取源码一、系统介绍 本系统实现了 普通用户&#xff1a;歇后语大全、歇后语排行榜、歇后语管理 管理员用户&#xff1a;歇后语大全、歇后语排行…