实测对比:PyInstaller vs Nuitka打包后,用这个工具分别能瘦身多少?附Matplotlib/Numpy案例
PyInstaller vs Nuitka 终极瘦身实测:Matplotlib/Numpy 项目体积优化全记录
每次用 Python 写完工具准备分享给同事时,最尴尬的莫过于对方看到几百兆的安装包后那句"这真是个小工具吗?"。上周我用 Matplotlib 做了个简单的数据可视化工具,PyInstaller 打包后居然有 42MB,而同样的功能用 C++ 编译出来可能不到 5MB。这促使我系统性地测试了主流 Python 打包方案及其瘦身技巧,特别是当项目包含科学计算库时的优化空间。
1. 测试环境与基准建立
1.1 实验设计思路
为了获得可比性数据,我设计了一个标准测试用例:一个使用 Numpy 生成随机数据并用 Matplotlib 绘制散点图的脚本。选择这两个库是因为它们:
- 是数据科学项目的典型依赖
- 包含大量可能未使用的编译文件和资源
- 在不同打包工具中的处理方式差异明显
测试环境配置如下:
| 环境项 | 配置详情 |
|---|---|
| 操作系统 | Windows 11 22H2 |
| Python 版本 | 3.9.13 (Miniconda 环境) |
| 测试库版本 | Numpy 1.23.5, Matplotlib 3.6.2 |
| 打包工具版本 | PyInstaller 5.7.0, Nuitka 1.4.6 |
1.2 基准代码实现
# scatter_plot.py import numpy as np import matplotlib.pyplot as plt def generate_plot(): np.random.seed(2023) x = np.random.normal(0, 1, 500) y = np.random.normal(0, 1, 500) plt.style.use('ggplot') fig, ax = plt.subplots(figsize=(8,6)) ax.scatter(x, y, alpha=0.6, c=np.sqrt(x**2 + y**2), cmap='viridis') ax.set_title('Multivariate Normal Distribution', pad=20) ax.set_xlabel('X Value', labelpad=10) ax.set_ylabel('Y Value', labelpad=10) plt.savefig('scatter.png', dpi=120, bbox_inches='tight') plt.close() if __name__ == '__main__': generate_plot() input("Plot generated. Press Enter to exit...")这段代码特意包含了 Matplotlib 的样式设置和复杂绘图选项,以确保打包时包含完整的库功能。
2. 初始打包体积对比
2.1 PyInstaller 打包结果
使用以下命令进行打包:
pyinstaller --onefile --clean --upx-dir=./upx-3.96-win64 scatter_plot.py打包结果分析:
| 文件类型 | 大小 (MB) | 占比 | 主要构成 |
|---|---|---|---|
| 原始 EXE | 42.7 | 100% | 包含所有依赖的独立可执行文件 |
| 解压后文件夹 | 158.2 | 370% | 运行时临时解压的全部内容 |
| 关键子目录 | |||
| ├─matplotlib | 89.3 | 56.4% | 字体、样式、后端驱动等 |
| ├─numpy | 37.8 | 23.9% | 核心库及扩展模块 |
| └─PIL | 12.1 | 7.6% | 图像处理相关依赖 |
2.2 Nuitka 打包结果
使用以下命令进行打包:
nuitka --onefile --remove-output --plugin-enable=numpy --windows-icon-from-ico=icon.ico scatter_plot.py打包结果对比:
| 指标 | PyInstaller | Nuitka | 差异 |
|---|---|---|---|
| EXE 文件大小 | 42.7MB | 38.2MB | -10.5% |
| 启动时间 | 2.3s | 1.1s | -52.2% |
| 内存占用 | 145MB | 112MB | -22.8% |
| 依赖文件数量 | 327个 | 291个 | -11.0% |
Nuitka 的打包优势主要来自:
- 更智能的依赖分析
- C++ 编译优化
- 动态链接部分系统库
3. 瘦身工具实战应用
3.1 瘦身工具工作原理
我们使用的瘦身工具基于动态分析原理,其工作流程如下:
运行监控阶段:
- 启动待分析的可执行文件
- 使用 API Hook 技术记录所有文件访问操作
依赖分析阶段:
- 生成实际使用的文件清单
- 对比打包目录中的全部文件
清理优化阶段:
- 移动未使用的文件到备份目录
- 生成文件迁移报告
- 保留原始目录结构
3.2 PyInstaller 瘦身过程
操作步骤:
python shrink_tool.py --target=dist/scatter_plot --log-level=debug关键日志输出:
[DEBUG] 开始分析目录: dist/scatter_plot [INFO] 检测到 PyInstaller 打包结构 [DEBUG] 监控到已加载模块: numpy.core._multiarray_umath [DEBUG] 监控到资源访问: matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf [WARNING] 未使用文件: matplotlib/backends/_backend_tk.cp39-win_amd64.pyd瘦身前后对比:
| 项目 | 瘦身前 | 瘦身后 | 缩减比例 |
|---|---|---|---|
| 总文件数 | 327 | 184 | 43.7% |
| 文件夹大小 | 158.2MB | 67.5MB | 57.3% |
| 运行时内存 | 145MB | 138MB | 4.8% |
3.3 Nuitka 瘦身效果
针对 Nuitka 的特殊处理:
# Nuitka 专用白名单配置 { "nuitka_specific": [ "*.dist/Microsoft.VC140.CRT/*", "*.dist/python39.dll", "*.dist/zlib.pyd" ], "always_keep": [ "**/site-packages/numpy/core/*.dll", "**/matplotlib/backends/backend_qt5agg.py" ] }优化结果:
| 优化阶段 | 文件大小 | 较原始缩减 |
|---|---|---|
| 初始打包 | 38.2MB | - |
| 基础瘦身 | 29.7MB | 22.3% |
| 手动优化后 | 24.1MB | 36.9% |
| UPX 压缩后 | 18.4MB | 51.8% |
4. 深度优化技巧
4.1 依赖树修剪技术
通过pipdeptree分析发现可以优化的依赖:
pipdeptree --packages numpy,matplotlib --freeze | grep -v "^\s"输出显示可以移除的间接依赖:
mkl-service==2.4.0 pyparsing==3.0.9 cycler==0.11.0优化方法:
# setup.cfg 中添加排除项 [options] install_requires = numpy>=1.21 matplotlib>=3.5 exclude_packages = mkl pyparsing4.2 资源文件选择性打包
Matplotlib 字体优化方案:
创建自定义字体清单:
# font_list.py from matplotlib import font_manager fonts = [f.name for f in font_manager.fontManager.ttflist if f.name in ('DejaVu Sans', 'Arial')] print(f"Required fonts: {fonts}")修改打包配置:
# hook-matplotlib.py datas = [ ('mpl-data/fonts/ttf/DejaVuSans.ttf', 'matplotlib/mpl-data/fonts/ttf'), ('mpl-data/matplotlibrc', 'matplotlib/mpl-data') ]
4.3 二进制文件压缩对比
测试不同压缩工具效果:
| 工具 | 压缩级别 | PyInstaller 大小 | Nuitka 大小 | 耗时 |
|---|---|---|---|---|
| 无压缩 | - | 42.7MB | 38.2MB | - |
| UPX | --best | 31.5MB (-26.2%) | 28.1MB (-26.4%) | 45s |
| Aspack | Max | 29.8MB (-30.2%) | 26.4MB (-30.9%) | 68s |
| MPRESS | Ultra | 28.3MB (-33.7%) | 25.1MB (-34.3%) | 52s |
注意:过度压缩可能导致杀毒软件误报,建议测试后选择平衡方案
5. 工程实践建议
经过两周的测试和十几次打包迭代,总结出以下实用经验:
工具选型策略:
- 快速原型开发 → PyInstaller(调试方便)
- 生产环境发布 → Nuitka(性能更优)
- 极致体积需求 → PyInstaller + 深度瘦身
目录结构优化:
dist/ ├── app.exe # 主程序 ├── runtime/ # 动态加载的依赖 │ ├── numpy/ # 科学计算库 │ └── matplotlib/ # 绘图相关 └── data/ # 资源文件持续集成配置:
# .github/workflows/build.yml - name: Build with Nuitka run: | nuitka --onefile --include-package-data=matplotlib \ --remove-output --output-dir=dist src/app.py python shrink_tool.py --target=dist/app.dist \ --config=.shrinkrc.json upx --lzma --best dist/app.exe
