一篇关于利用numba加速python运行效率的笔记

2023-05-16

一篇关于加速python代码运行效率的笔记

        • 一、原始代码(部分)分析
        • 二、变量预分配内存实现加速
        • 三、numba装饰器实现加速
          • 3.1 为什么numba可以对python代码加速?
          • 3.2 修改代码匹配numba的类型支持
        • 四、其它尝试
          • 4.1 多线程的思考
          • 4.2 数据结构的其它尝试
        • 参考文章

  • 问题描述: 最近在跑一段python代码,代码的主要功能是对fastq格式的基因数据通过2阶Markov过程构建一个频率表,频率表的格式大概是{(34,45): [23,2, 35,6, 23,1], (23,35): [33,1]}这种形式,可以简化为(34,45,23,2),最后一位为(34,45,23)三元组在数据集中出现的次数。在计算过程中涉及到大量的for循环操作,在数据集为200M时,耗时将近3小时(单线程。硬件:),导师说numba对于for循环可以有很好的加速效果,试试用numba把程序降到30分钟。
  • 最终加速效果: 程序由最开始的2.5小时下降到2分钟
  • 主要加速路线:
    • 变量预分配内存实现加速:2.5小时 to 25分钟;
    • numba装饰器实现加速:25分钟 to 2分钟。

一、原始代码(部分)分析

    total_entropy = 0
    freq_dict = {}
    row_mean_q = []
    for k in range(row_size):
        row_mean[k] = quan_normal(row_mean[k])
        row_mean_q.append(quan_quan4(row_mean[k],row_mean))
    for j in range(4,matrix.shape[1]):
        for i in range(2,matrix.shape[0]):
            context_list = (max(matrix[i,j-2],matrix[i,j-1]),row_mean_q[i])
            if context_list not in freq_dict.keys():
                freq_dict[context_list] = {matrix[i,j]:1}
                total_entropy += 8
            else:
                if matrix[i,j] not in freq_dict[context_list].keys():
                    freq_dict[context_list].update({matrix[i,j]:1})
                    total_entropy += 8
                else:
                    total_entropy += -np.log(freq_dict[context_list][matrix[i,j]]/np.sum(list(freq_dict[context_list].values())))
                    freq_dict[context_list][matrix[i,j]] += 1

说明:

  • 利用第三方包line_profiler对程序进行程序运行时间分析,发现row_mean_q和for循环中最后一个total_entropy的计算花费了主要的程序运行时间;
    在这里插入图片描述

  • 考虑应该是row_mean_q在最开始在定义时是一个空list类型,通过for循环不断地向list中追加数据,并且由于调用的函数quan_quan4()是一个比较简单的分段判断函数计算量很小不会花费太多时间,所以猜测在开辟内存过程中花费了大量的时间,并且通过list.append()方法生成最终的list本身就很不高效。


二、变量预分配内存实现加速

基于上面的猜测,尝试修改变量row_mean_q的赋值方式,在预定义时直接赋值,然后后面逐项修改,具体地如代码所示:

    #row_mean_q =  []
    row_mean_q = row_mean   #row_mean是一个已计算好且与row_mean_q数组长度、每个元素都较为接近的list
    for k in range(row_size):
        row_mean[k] = quan_normal(row_mean[k])
        #row_mean_q.append(quan_quan4(row_mean[k],row_mean))
        row_mean_q[k] = quan_quan4(row_mean[k])

说明:

  • 这里为了更好地利用预分配的内存,直接令row_mean_q = row_mean,row_mean是一个在程序前面部分已计算好的,且与row_mean_q数组长度、每个元素都较为接近的list。这样赋值后,在后续进行元素值替换时,内存几乎不发生变化(这里需要验证:python中list元素替换时如何实现的,新分配内存还是在原地址进行扩展?)

    • 经验证,python中变量每次初始化时会开辟新的内存空间,并将内容地址赋值给变量。
    • 数据内部结构改变时(增删改),内存地址不发生变化(可通过id(a)查看变量a的内存地址),但占用内存空间大小可能发生变化(可通过sys.getsizeof(a)来查看内存大小)。如果对变量重新初始化赋值,就会将新的地址赋值给变量。
  • 另一个需要验证的是:如果使用row_mean_q = [None]*length的形式进行赋值,两者最终的运行时间是否有差别,差别有多大?

    • 经验证,两种不同的赋值,后者占用的运行时间稍多,但差距不大。一方面,前者直接将row_mean的内容地址赋值给row_mean_q,后者则需要在内存中开辟新的内存;另一方面,由于None占用内存较小,在后续元素重新赋值过程中需要扩张内存。
    • 但需要注意的是,通过row_mean_q = row_mean两者指向同一个列表内存地址,而当对列表内容进行操作时,列表本身的地址没有发生变化,也就说修改row_mean_q和row_mean中任何一个的元素值,都会导致另一个同样发生变化。

三、numba装饰器实现加速

3.1 为什么numba可以对python代码加速?

参考博客:Python程序提速神器–[Numba快速上手指南] (https://blog.csdn.net/june_young_fan/article/details/103671449)

计算机只能执行二进制的机器码,C,C++等编译型语言依靠编译器将源代码转化为可执行文件后运行;Python,Java等解释型语言使用解释器将源码翻译后在虚拟机上执行。对于python,由于解释器的存在,其执行效率比C语言慢几倍甚至几十倍。

Python 是一门解释语言, Python 为我们提供了基于硬件和操作系统的一个虚拟机,并使用解释器将源代码转化为虚拟机可执行的字节码。字节码在虚拟机上执行,得到结果。

解决python执行效率低问题的解决方案:

  • 用C或C++ 重写Python函数。要求对C和C++语言熟悉;
  • 用JIT(just in time) 技术。

Numba是一个针对Python的开源JIT编译器,由Anaconda公司主导开发,可以对原生代码进行cpu和gpu加速。

下图是一张由python解释器进行程序运行的过程与JIT编译器运行python程序过程的比较:
在这里插入图片描述

具体地,我们使用 python example.py 来执行一份源代码时, Python 解释器会在后台启动一个字节码编译器( Bytecode Compiler ),将源代码转换为字节码。字节码是一种只能运行在虚拟机上的文件, Python 的字节码默认后缀为 .pyc , Python 生成 .pyc 后一般放在内存中继续使用,并不是每次都将 .pyc 文件保存到磁盘上。有时候我们会看到自己 Python 代码文件夹里有很多 .pyc 文件与 .py 文件同名,但也有很多时候看不到 .pyc 文件。pyc 字节码通过 Python 虚拟机与硬件交互,关于.pyc文件的作用和更新机制请移步Python程序的执行过程(解释型语言和编译型语言)。虚拟机的出现导致程序和硬件之间增加了中间层,运行效率大打折扣。相信使用过虚拟机软件的朋友深有体会,在原生的系统上安装一个虚拟机软件,在虚拟机上再运行一个其他系统,经常感觉速度下降,体验变差,这与 Python 虚拟机导致程序运行慢是一个原理。

Just-In-Time(JIT) 技术为解释语言提供了一种优化,它能克服上述效率问题,极大提升代码执行速度,同时保留 Python 语言的易用性。使用 JIT 技术时,JIT 编译器将 Python 源代码编译成机器直接可以执行的机器语言,并可以直接在 CPU 等硬件上运行。这样就跳过了原来的虚拟机,执行速度几乎与用 C 语言编程速度并无二致。

延伸阅读:[How Numba and Cython speed up Python code] (https://rushter.com/blog/numba-cython-python-optimization/)文中提到Cython对python语言加速与numba的区别及适用场景。

3.2 修改代码匹配numba的类型支持

使用numba对程序进行加速的主要问题是:目前Numba只支持python原生函数和部分Numpy函数。

通过实际编写程序,过程中遇到的主要问题:

  • 通过jit进行装饰的函数,如果函数中嵌套有其它函数同样需要进行jit装饰;
  • numba不支持动态变量,在函数内部使用变量必须预先定义,也可以在函数外部定义,但必须通过形参传输到函数内部,不然会外部变量作为常值的全局变量;
  • list的数据类型必须转换为numpy.array类型;
  • dict类型变量在使用前必须以numba支持的数据类型格式进行声明,python中一般形式的dict在被jit装饰的函数中无法使用;
  • 貌似对tuple的数据类型支持较好,不需要提前声明

基于以上几点对程序进行修改,修改结果如下:

    freq_dict = numba.typed.Dict.empty(
        key_type = numba.types.UniTuple(numba.types.int32,2),
        value_type = numba.types.int32[:],)
    row_mean_q = row_mean
    for k in range(row_size):
        row_mean[k] = quan_normal(row_mean[k])
        row_mean_q[k] = quan_quan4(row_mean[k])
    row_mean_q = np.array(row_mean_q,dtype=np.int32)

    @numba.njit
    def caculate_directory(freq_dict,total_entropy,row_mean_q):
        for j in range(4,matrix.shape[1]):
            for i in range(2,matrix.shape[0]):
                context_list = (max(matrix[i,j-2],matrix[i,j-1]),row_mean_q[i])
                if context_list not in freq_dict:
                    freq_dict[context_list] = np.array([matrix[i,j],1],dtype=np.int32)
                    total_entropy += 8
                else:
                    if matrix[i,j] not in list(freq_dict[context_list])[::2]:
                        freq_dict[context_list] = np.array(list(freq_dict[context_list])+[matrix[i,j],1],dtype=np.int32)
                        total_entropy += 8
                    else:
                        _= (list(freq_dict[context_list])[::2].index(matrix[i,j]))*2
                        total_entropy += -np.log(freq_dict[context_list][_+1]/(freq_dict[context_list][1::2]).sum())
                        freq_dict[context_list][_+1] += 1
        return total_entropy

说明:

  • 这里代码之所以可以修改为最终上述版本,和程序要解决的问题有很大关系。原代码中freq_dict是字典类型的,具体如{(34,45):{23:2,35:6,23:1},(23,35):{33,1}}也即字典嵌套字典的形式,很可惜,就目前的了解来看,numba似乎不支持这种复杂的数据类型,尝试将其修改为另外一种字典形式{(34,45):[23,2, 35,6, 23,1],(23,35):[33,1]}也即字典套list的形式。原来字典套字典的freq_dice中对于每一个键值对,值同样采用dict的形式,并且值中的键值对由于key的值是相异的,所以我们当按照元素出现顺序将原来的值中dict{key1:value1,key2:value2,…}修改为[key1,value1,key2,value2,…]list的形式,虽然在进行查询时虽然不能继续运用key-value的形式查询,但可以用奇偶位置的元素进行查询,并且由于奇数位的元素是各异的,所以可以利用list.index('x')返回元素x位于list中的位置,方便后续进一步的元素操作。

四、其它尝试

4.1 多线程的思考
4.2 数据结构的其它尝试

参考文章

[1] Python程序提速神器–Numba快速上手指南
[2] How Numba and Cython speed up Python code

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一篇关于利用numba加速python运行效率的笔记 的相关文章

随机推荐

  • uniapp实现微信登录

    项目描述 使用uniapp框架编写微信小程序 xff0c 使用自己的后端 xff0c 实现微信登陆功能 登录流程 此处参考微信官网提供的 小程序登录流程时序 如下图 xff1a 图片来源 xff1a 微信官方API文档 所以登录的流程即 x
  • 程序设计大赛试题及答案

    Problem A 比赛须知 Description 小邯来参加邯郸学院大学生程序设计竞赛 由于这场比赛在线上举行 xff0c 有很多需要遵守的规则 有一条规则是 xff0c 为了避免对题目内容相关的提问被无关的提问淹没 xff0c 所有和
  • 使用uni-app框架中uni.chooseAddress()接口,获取不到用户收货地址

    错误描述 在我们使用uni app框架或微信原生开发微信小程序时 使用到uni chooseAddress OBJECT 接口获取用户收货地址时 无法跳转到收货地址页面获取 打印接口返回信息 显示 chooseAddress fail th
  • 软件工程-部分测试概念

    1 黑盒测试法 黑盒测试法也称功能测试 xff0c 这种方法将被测程序看成一个黑盒子 xff0c 测试人员完全不考虑程序的内部结构和处理过程 也就是说 xff0c 黑盒测试是在程序接口进行的测试 xff0c 它只检查程序功能是否按照需求规格
  • linux下安装libpcap

    1 安装GCC xff1a yum y install gcc c 43 43 2 安装flex xff1a yum y install flex 没有flex xff0c 直接安装libpcap会提示 34 Your operating
  • Spring练习题

    作业 简答题 Spring 是什么 xff1f 谈谈你对IOC的理解简述Spring IOC的启动过程说出bean工厂创建bean的三种方式 xff1f 在Spring中 xff0c bean的注入有几种方式 xff0c 各是什么 xff1
  • CentOS 7 安装XAMPP

    以下步骤如果包含Linux命令 xff0c 没有特别说明均在root下运行 1 首先安装CentOS xff0c 下载ISO的网址如下 xff0c 挑选最快的镜像站点下载 xff1a http isoredirect centos org
  • 实验二 使用CSS样式美化购物列表页面中的菜单导航以及商品展示

    一 实验目的 掌握CSS定义文字 背景图片 超链接控制 列表等常用属性的设置 二 实验要求 在购物列表页面中 xff0c 通过 lt ul gt 标签来实现菜单导航栏 xff0c 然后使用css样式控制菜单栏的位置和样式 xff0c 效果如
  • SpringBoot+Vue项目准妈妈孕期交流平台

    文末获取源码 开发语言 xff1a Java 开发工具 IDEA Eclipse 数据库 MYSQL5 7 使用框架 springboot 43 vue JDK版本 xff1a jdk1 8 前言介绍 系统实现管理员 xff1a 首页 个人
  • SpringBoot+Vue项目社区团购系统

    文末获取源码 开发语言 xff1a Java 框架 xff1a springboot JDK版本 xff1a JDK1 8 服务器 xff1a tomcat7 数据库 xff1a mysql 5 7 8 0 数据库工具 xff1a Navi
  • SSM+Vue+Element-UI实现员工工资管理系统

    文末获取源码 开发语言 xff1a Java 框架 xff1a ssm JDK版本 xff1a JDK1 8 服务器 xff1a tomcat7 数据库 xff1a mysql 5 7 8 0 数据库工具 xff1a Navicat11 开
  • 【Android Studio】使用Binding代替R.layout.xxx显示kotlin.UninitializedPropertyAccessException(已解决)

    初始代码 xff1a span class token keyword class span span class token class name MainActivity span span class token operator s
  • vim编辑器重要快捷键及vim设置

    一 快捷键 1 全选 xff1a ggVG 2 跳转到文本最后一行 xff1a shift 43 g 3 跳转到文本第一行 xff1a gg 4 跳转到光标所选行的行首位置 xff1a 0 5 跳转到光标所选行的行尾位置 xff1a shi
  • 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系

    使用 sudo apt get install 安装软件时 xff0c 出现错误 无法修正错误 xff0c 因为您要求某些软件包保持现状 xff0c 就是它们破坏了软件包间的依赖关系 错误的主要原因是 xff0c 系统中已经安装了被依赖的包
  • import _ssl # if we can‘t import it, let the error propagate

    转载链接 xff1a https blog csdn net u013398960 article details 107524068 实测有用
  • vscode 终端美化

    1 进入网站 Base16 Terminal Colors for Visual Studio Code 2 选择自己喜欢的主题 点击Copy to clipboard 3 打开vscode 设置 输入setting 在 settings
  • vue项目,如何关闭eslint检测?多种解决办法

    点击下方查看博主 博主
  • 关键字 'with' 附近有语法错误。

    最近我在开发中遇到个挺棘手的问题 xff0c 一段T SQL语句在开发环境中明明跑得好好的 xff0c 发布到生产环境却报错 经过排查 xff0c 只有WinXP下才会出现 xff0c 即使是在干净的虚拟机环境中 客户端不管是用C 还是De
  • 通过Gitee绑定域名到CSDN

    目录 申请gitee账号 新建仓库 新建index html文件 设置Gitee Pages 使用CSDN发布博客文章 xff0c 如果别浏览我的文章 xff0c 直接给别人我自己的CSDN链接太长 xff0c 而且也没人能记得住 xff0
  • 一篇关于利用numba加速python运行效率的笔记

    一篇关于加速python代码运行效率的笔记 一 原始代码 部分 分析二 变量预分配内存实现加速三 numba装饰器实现加速3 1 为什么numba可以对python代码加速 xff1f 3 2 修改代码匹配numba的类型支持 四 其它尝试