我使用python的setuptools进行了一次奇怪的150ms启动惩罚,我构建了一个最小的测试用例并且问题仍然存在:
这个最小案例的项目布局是:
- setup.py
- setuptest
- - __init__.py
- - __main__.py
setup.py文件包含:
from setuptools import setup
setup(
name = 'setuptest',
version = '0.1',
packages = ['setuptest'],
entry_points = {
'console_scripts' : ['setuptest = setuptest.__main__:main']
} ,
)
__main__.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
并执行:
—— — time setuptest
hai
real 0m0.158s
user 0m0.144s
sys 0m0.012s
—— —
给我158ms。这个+ 150s的启动延迟时间是一致的,当我使用setuptools时会发生这种情况,但是我没有通过软件包管理器安装或手动安装其他人的项目,这让我失望认为我显然做得非常错误。
答案 0 :(得分:2)
好吧,当您使用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()
实现:
来自setuptools.py: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
来自setuptools.py:Distribution.load_entry_point()
:
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()
来自setuptools.py:Distribution.get_entry_info()
:
def get_entry_info(self, group, name):
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name)
我将把它留在那里,你可以跟进它变得昂贵的地方。我想在Distribution
中完成映射的方法(如_dep_map
属性)在执行时可能非常昂贵。
答案 1 :(得分:0)
如果您使用'python setup.py install'或'pip install'从源代码安装项目。 (创建.egg文件)生成的可执行脚本使用pkg_resources,这很慢。
但是,如果首先构建二进制轮文件(.whl)然后安装轮,则生成的可执行脚本似乎不会从pkg_resources导入并且速度更快。以任意项目为例,这是使用两种不同的方法安装cookiecutter项目的结果。
https://github.com/audreyr/cookiecutter
如果使用'python setup.py install'从源代码安装此项目,则生成的可执行脚本包含pkg_resources的导入(并且速度很慢):
#!/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')()
)
但是,如果使用以下两个命令构建并安装了wheel文件:
python setup.py bdist_wheel
pip install dist/cookiecutter-1.5.1-py2.py3-none-any.whl
可执行脚本不包含pkg_resources的导入(并且速度更快):
#!/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())
答案 2 :(得分:-1)
好吧,我自己找到了一个令我感到困惑的答案,但它显示了为什么其他人更快。事实证明,即使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
没有方便的入口点机制。