PyInstaller能带来性能的提升么?

868 阅读3分钟

博客首发于: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 Truefor 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跨平台支持的相关内容,我们后面再做研究,下期博客见~