博客首发于:www.weeco.tech/e8efcd66929…
PyInstaller是一个将Python脚本转换为二进制exe文件的工具,生成之后的二进制文件能够在不同的平台上进行使用,那么有一个疑问,它能带来程序性能的提升么?今天我们就通过一个实验来探究一下~
实验环境
系统:CentOS 8.2 虚拟机
CPU:Intel(R) Xeon(R) Gold 6132 CPU @ 2.60GHz 直通
Python:3.6.8
gcc:8.5.0-19
PyInstaller:4.10
测试所用代码
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
for i in range(2, 1000000):
if is_prime(i):
print(i)
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool is_prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
for (int i = 2; i < 1000000; i++) {
if (is_prime(i)) {
printf("%d\n", i);
}
}
return 0;
}
PyInstaller安装及使用
pip3 install pyinstaller
pyinstaller -F prime_num.py
如下是PyInstaller执行过程中的输出。
[root@localhost ~]# pyinstaller -F prime_num.py
54 INFO: PyInstaller: 4.10
54 INFO: Python: 3.6.8
55 INFO: Platform: Linux-4.18.0-193.el8.x86_64-x86_64-with-centos-8.2.2004-Core
55 INFO: wrote /root/prime_num.spec
57 INFO: UPX is not available.
58 INFO: Extending PYTHONPATH with paths
['/root']
192 INFO: checking Analysis
192 INFO: Building Analysis because Analysis-00.toc is non existent
192 INFO: Initializing module dependency graph...
193 INFO: Caching module graph hooks...
200 INFO: Analyzing base_library.zip ...
2880 INFO: Caching module dependency graph...
2989 INFO: running Analysis Analysis-00.toc
3014 INFO: Analyzing /root/prime_num.py
3016 INFO: Processing module hooks...
3017 INFO: Loading module hook 'hook-difflib.py' from '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks'...
3018 INFO: Loading module hook 'hook-encodings.py' from '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks'...
3064 INFO: Loading module hook 'hook-heapq.py' from '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks'...
3065 INFO: Loading module hook 'hook-pickle.py' from '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks'...
3066 INFO: Loading module hook 'hook-xml.py' from '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks'...
3263 INFO: Looking for ctypes DLLs
3263 INFO: Analyzing run-time hooks ...
3265 INFO: Including run-time hook '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks/rthooks/pyi_rth_subprocess.py'
3266 INFO: Including run-time hook '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks/rthooks/pyi_rth_pkgutil.py'
3268 INFO: Including run-time hook '/usr/local/lib/python3.6/site-packages/PyInstaller/hooks/rthooks/pyi_rth_inspect.py'
3272 INFO: Looking for dynamic libraries
3496 INFO: Looking for eggs
3497 INFO: Using Python library /lib64/libpython3.6m.so.1.0
3500 INFO: Warnings written to /root/build/prime_num/warn-prime_num.txt
3516 INFO: Graph cross-reference written to /root/build/prime_num/xref-prime_num.html
3544 INFO: checking PYZ
3544 INFO: Building PYZ because PYZ-00.toc is non existent
3544 INFO: Building PYZ (ZlibArchive) /root/build/prime_num/PYZ-00.pyz
3834 INFO: Building PYZ (ZlibArchive) /root/build/prime_num/PYZ-00.pyz completed successfully.
3836 INFO: checking PKG
3837 INFO: Building PKG because PKG-00.toc is non existent
3837 INFO: Building PKG (CArchive) prime_num.pkg
6040 INFO: Building PKG (CArchive) prime_num.pkg completed successfully.
6041 INFO: Bootloader /usr/local/lib/python3.6/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
6042 INFO: checking EXE
6042 INFO: Building EXE because EXE-00.toc is non existent
6042 INFO: Building EXE from EXE-00.toc
6042 INFO: Copying bootloader EXE to /root/dist/prime_num
6042 INFO: Appending PKG archive to custom ELF section in EXE
6053 INFO: Building EXE from EXE-00.toc completed successfully.
执行之后,会在当前环境下生成build、dist、__pycache__文件夹及prime_num.spec文件。最终生成的二进制文件位于dist文件夹下,其文件名与python脚本文件名一致。
查看了一下,该二进制文件的ldd信息如下:
# ldd prime_num
linux-vdso.so.1 (0x00007ffe77eec000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f55da464000)
libz.so.1 => /lib64/libz.so.1 (0x00007f55da24c000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f55da02c000)
libc.so.6 => /lib64/libc.so.6 (0x00007f55d9c67000)
/lib64/ld-linux-x86-64.so.2 (0x00007f55da668000)
计算结果与性能数据对比
将原始Python脚本执行结果与生成的二进制文件的执行结果进行对比,其结果一致。
# ./prime_num > /tmp/prime_num_pyinstall.log
# python3 prime_num.py > /tmp/prime_num_python.log
# md5sum /tmp/prime_num_py*
c13929ee9d2aea8f83aa076236079e94 /tmp/prime_num_pyinstall.log
c13929ee9d2aea8f83aa076236079e94 /tmp/prime_num_python.log
由PyInstaller生成的二进制执行时间为4.49秒;
直接Python执行的时间为4.35秒;较PyInstaller生成的二进制执行时间短。
为了更好地进行对比,我们用C语言实现了同样的功能,其代码为:
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool is_prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
for (int i = 2; i < 1000000; i++) {
if (is_prime(i)) {
printf("%d\n", i);
}
}
return 0;
}
编译所使用的指令为:
gcc prime_num.c -O3 -o prime_num_c -lm
通过MD5值证明,由C代码编译出来的二进制计算结果与Python代码一致。其计算时间为0.17秒,甚至不到Python脚本执行时间的4%。
为了更深入地分析PyInstaller生成的二进制与C代码编译出来的二进制性能差异,我们通过objdump -S指令查看了两者的汇编代码并进行了简单的分析。
由PyInstaller生成的二进制,通过objdump看到其汇编指令共有5966行,而直接由C代码编译出来的二进制文件,则仅有303行。两者相差近20倍。
相比而言,C代码编译出来的二进制,其汇编语言代码相比PyInstaller的简洁很多;
此外,对比了两者的文件大小,C代码直接编译生成的二进制,其文件大小为18K,而PyInstaller生成的二进制文件大小为6.0M,两者相差342倍。
结论
PyInstaller生成的二进制文件执行时间略长于Python执行脚本的时间,即PyInstaller不但无法缩短执行时间,反而会使执行时间变长。
当然,PyInstaller的主要目的是隐藏Python源码,减少环境依赖,以及实现Python跨平台。
关于PyInstaller跨平台支持的相关内容,我们后面再做研究,下期博客见~