使用python setuptools时会出现奇怪的~150ms启动惩罚

2024-05-16 12:13:27 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用python的setuptools遇到了一个奇怪的150ms启动惩罚,我构建了一个最小的测试用例,问题仍然存在:

我的项目布局是:

- setup.py
- setuptest
- - __init__.py
- - __main__.py

那个设置.py文件包含:

from setuptools import setup

setup(
    name         = 'setuptest',
    version      = '0.1',
    packages     = ['setuptest'],

    entry_points = {
        'console_scripts' : ['setuptest = setuptest.__main__:main']
        } ,
    )

\uuuu main\uuuuuuuuy.py文件只包含:

#!/usr/bin/env python2

def main ():
    print "hai"

if __name__ == '__main__':
    main()

在项目根目录中执行此操作:

 —— — time python2 setuptest
hai

real    0m0.021s
user    0m0.017s
sys     0m0.004s

但是,在运行sudo python2 setup.py install并执行以下操作之后,脚本的总执行时间为21毫秒:

 —— — time setuptest 
hai

real    0m0.158s
user    0m0.144s
sys     0m0.012s
 —— — 

给我158ms。这个+150s的启动延迟时间是一致的,在使用setuptools时会发生,但在我通过包管理器安装的东西或手动安装其他人的项目时不会发生,这让我觉得我显然做了一些非常错误的事情。你知道吗


Tags: 文件项目namepytimemainsyssetup
3条回答

如果您使用“python”或“python”从源代码安装项目设置.pyinstall'或'pip install'(创建一个.egg文件)生成的可执行脚本使用pkg\u资源,速度很慢。你知道吗

但是,如果先构建二进制wheel文件(.whl),然后安装wheel,则生成的可执行脚本似乎不会从pkg\u资源导入,而且速度更快。以一个任意项目为例,下面是使用两种不同方法安装cookiecutter项目的结果。你知道吗

https://github.com/audreyr/cookiecutter

如果此项目是使用“python”从源代码安装的设置.pyinstall',生成的可执行脚本包含从pkg\u资源导入的内容(速度慢):

#!/usr/local/opt/python3/bin/python3.5
# EASY-INSTALL-ENTRY-SCRIPT: 'cookiecutter==1.5.1','console_scripts','cookiecutter'
__requires__ = 'cookiecutter==1.5.1'
import re
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(
        load_entry_point('cookiecutter==1.5.1', 'console_scripts', 'cookiecutter')()
    )

但是,如果使用以下两个命令生成并安装了控制盘文件:

python setup.py bdist_wheel
pip install dist/cookiecutter-1.5.1-py2.py3-none-any.whl

可执行脚本不包含来自pkg\u资源的导入(速度更快):

#!/usr/local/opt/python3/bin/python3.5

# -*- coding: utf-8 -*-
import re
import sys

from cookiecutter.__main__ import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

当您使用setuptools安装软件时,它将在bin目录中生成可执行脚本,如下所示:

import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.exit(
        load_entry_point('<PACKAGE_NAME>', 'console_scripts', '<ENTRY_POINT>')()
    )

因为load_entry_point()将通过sys.path中所有可用的包进行解析,所以安装的位置和包越多,构建列表并查找列表所需的时间就越长。你知道吗

有关更多详细信息,我们需要了解setuptools的load_entry_point()实现:

^{}

def load_entry_point(dist, group, name):
    """Return `name` entry point of `group` for `dist` or raise ImportError"""
    return get_distribution(dist).load_entry_point(group, name)

'setuptools.py:get_distribution()'

def get_distribution(dist):
    """Return a current distribution object for a Requirement or string"""
    if isinstance(dist,basestring): dist = Requirement.parse(dist)
    if isinstance(dist,Requirement): dist = get_provider(dist)
    if not isinstance(dist,Distribution):
        raise TypeError("Expected string, Requirement, or Distribution", dist)
    return dist

^{}

def load_entry_point(self, group, name):
    """Return the `name` entry point of `group` or raise ImportError"""
    ep = self.get_entry_info(group,name)
    if ep is None:
        raise ImportError("Entry point %r not found" % ((group,name),))
    return ep.load()

^{}

def get_entry_info(self, group, name):
    """Return the EntryPoint object for `group`+`name`, or ``None``"""
    return self.get_entry_map(group).get(name)

我会把它放在那里,你可以跟踪到它变得昂贵的地方。我猜在^{}中完成映射的方法(比如^{}属性)在执行时可能会非常昂贵。你知道吗

好吧,我自己找到了答案,这让我很困惑,但它说明了为什么其他人更快。事实证明,尽管setuptools被推荐为更新更好的,但出于某种原因,它也会增加巨大的性能损失,至少在我的系统上distutils没有。你知道吗

所有快速运行的包都使用了distutils

将示例编辑为:

from distutils.core import setup

setup(
    name         = 'disttest',
    version      = '0.1',
    packages     = ['disttest'],

    scripts      = ['bin/disttest']
    )

其中bin/disttest是项目根目录中的一个可执行文件,它作为实际程序的一个普通包装器,完全解决了问题。从distutils.core导入而不是从setuptools导入似乎是关键。遗憾的是distuitls没有方便的入口点机制。你知道吗

相关问题 更多 >