使用Pyinstaller封装Python脚本
NWPC 的业务系统中很少使用 Python 脚本,主要因为当前 HPC 上的 Python 环境安装和更新包不是很方便。
我常用的方式是在 HPC 的用户目录下自己安装一个 Python 环境,并使用代理在线安装软件包。 具体方法参见《HPC用户安装Python解决方案》。
但如果想在业务系统中使用安装在用户目录的 Python 环境,会有很多问题。
- 业务系统不应该依赖非业务账户的文件
- 业务账户不应该单独安装 Python 环境,可能会影响其他业务的正常运行
- 业务账户不应该使用代理连接互联网
除了以上几点外,我在测试时候发现另一个问题,也是用户账户下 Python 环境的最大问题:import加载速度太慢。 以笔者多个平台的使用经验来看,这个问题似乎是 CMA-PI 上独有的。
下面以最近正做的用于预测积分时间的 ecFlow 系统为例说明这个现象,并介绍如何通过 Pyinstaller 封装脚本实现快速加载。
背景
在之前的文章《NWPC笔记:预测模式积分时长 - 线性回归》基础上,构建 ecFlow 系统,实时读取模式积分输出,并计算积分总体耗时。
系统使用 nwpc-oper/nwpc-log-tool 获取积分耗时信息,并使用 scikit-learn 提供的线性回归方法预测总体耗时。
本文仅关注 Python 脚本 import 载入速度的问题。
现状
使用笔者个人账户下安装的 Python 环境,执行 Python 脚本,输出如下信息(有省略):
run script...Tue May 19 04:43:54 GMT 2020
begin...2020-05-19 04:45:32.081343
...skip..
finish...2020-05-19 04:45:38.785521
finish script...Tue May 19 04:45:56 GMT 2020
第一行和最后一行时间是 Shell 执行 Python 脚本前后,调用 date
命令的输出。
第二行和倒数第二行时间是 Python 脚本执行主函数开头和结尾处,使用 pandas.Timedelta.now()
打印的输出。
可以看到,主函数开始执行前,有将近 1 分 30 秒的等待时间,这部分主要消耗在执行 import
语句。
对比整个主函数执行只需要 6 秒,这部分等待时间已严重影响运行的效率。
另外,该系统需要实时获取数据,而模式积分一共耗时才 20 分钟。 在执行主函数前的等待过程中,模式积分仍会继续,很容易失去预测的提前量。
封装
如果等待时间延长是因为 import 时读取小文件太多导致的,那么如果将所有文件都打包到一个文件中,载入性能应该能有所提升。
Pyinstaller 就是一款用于打包 Python 程序,并生成可执行文件的工具。
CMA-PI 上使用 Pyinstaller 之前需要下载 upx 软件包,解压到某个目录,比如 UPX_DIR
中。
使用下面的语句在当前目录下的 dist
目录中生成可执行文件。
pyinstaller -F \
--upx-dir=${UPX_DIR} \
--hidden-import=sklearn.utils._cython_blas \
check_fcst_std_out.py
其中 -F
表示生成单个文件,--hidden-import
是强制包含没有显式引用的包,根据运行生成的可执行程序时候的错误提示而添加。
运行结束后,生成一个大小为 143MB 的可执行程序。
使用该程序替换 Python 脚本,运行输出如下:
run script...Tue May 19 12:43:55 GMT 2020
begin...2020-05-19 12:43:59.144919
...skil...
finish...2020-05-19 12:43:59.327985
finish script...Tue May 19 12:43:59 GMT 2020
执行主函数前的等待时间缩短为 4 秒,并且主函数完成后到 Python 程序退出的等待时间也变得可以忽略不计。
可见,封装 Python 脚本确实可以提高程序加载速度。
问题
简单的打包命令没有包含 Python 包中的数据文件,需要进行额外的设置。 笔者后续将进一步尝试 Pyinstaller 的各项功能。