奇怪〜使用python setuptools启动惩罚150ms

时间:2016-03-17 17:02:10

标签: python python-2.7 setuptools

我使用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并执行:

之后,总共执行了21毫秒的脚本
 —— — time setuptest 
hai

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

给我158ms。这个+ 150s的启动延迟时间是一致的,当使用setuptools时会发生这种情况,但是我没有通过软件包管理器安装或手动安装其他人的项目,这让我失望认为我显然做得非常错误。

3 个答案:

答案 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没有方便的入口点机制。