Openembedded Core Discussions
 help / color / mirror / Atom feed
* [PATCH 0/1] Improving setuptools performance in Python
@ 2020-06-23 22:58 Trevor Gamblin
  2020-06-23 22:58 ` [PATCH] python3-setuptools: patch entrypoints for faster initialization Trevor Gamblin
  2020-06-23 23:02 ` ✗ patchtest: failure for " Patchwork
  0 siblings, 2 replies; 3+ messages in thread
From: Trevor Gamblin @ 2020-06-23 22:58 UTC (permalink / raw)
  To: openembedded-core

As explained in https://github.com/pypa/setuptools/issues/510, the 
pkg_resources module in setuptools is very slow to initialize scripts 
due to how it scans the Python path for each module that would import 
pkg_resources. My understanding of upstream Python's response is that 
as of 3.7 and later, importlib.resources is available for replacing 
this functionality in new scripts, but existing code that uses 
pkg_resources will still have to be re-written to use it (or something 
like python3-fastentrypoints. 

I've done some performance testing with this patch using the following,
very simple steps:

1) Add "IMAGE_INSTALL_append = " python3-setuptools" to local.conf;

2) bitbake core-image-full-cmdline

3) mkdir -p test/minimal;

4) In test/minimal/__init__.py, add:

def main():
    print('hello world')

if __name__ == '__main__':
    main()

5) In test/setup.py:

from setuptools import setup, find_packages

setup(
name='minimal',
version='0.1',
python_requires='>=3.4',
packages=['minimal'],
entry_points={ 'console_scripts': [ 'minimal = minimal:main' ] },
)

6) Run "python3 setup.py install"

7) Run "time /usr/bin/minimal"

8) Note the drastic differences in run time, e.g. with
core-image-full-cmdline:

core-image-full-cmdline, WITHOUT setuptools ScriptWriter patch:

root@qemux86-64:~# time /usr/bin/minimal
hello world

real    0m0.198s
user    0m0.174s
sys     0m0.023s

core-image-full-cmdline, WITH setuptools ScriptWriter patch:

root@qemux86-64:~# time /usr/bin/minimal
hello world

real    0m0.034s
user    0m0.024s
sys     0m0.010s

I've also tried to ensure that this has no adverse effects on the larger
python ecosystem in Yocto, by running ptests on all modules in
meta-python that have ptests available and use setuptools, with this
patch installed. I saw no failures related to setuptools, although it is
still possible that this could have unforeseen consequences on other
modules.

Finally, some (truncated) profiling comparisons to emphasize the gains
in performance, using the same example module before and after the patch. 
There are three orders of magnitude difference in the number of calls 
required, including much more recursion. What is most apparent from 
these profiling results is how much more time the unpatched version 
spends parsing entry points when that effort isn't required to achieve
the same end. As can be seen from the simple example used here, the 
time savings at initialization are significant enough even for tiny
scripts that this fix should be incorporated in the project at the 
setuptools level, rather than requiring manual application, or 
modification of existing scripts on the user's end.

WITHOUT PATCH:

         134641 function calls (130543 primitive calls) in 0.723 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     88/1    0.002    0.000    0.723    0.723 {built-in method builtins.exec}
        1    0.000    0.000    0.723    0.723 minimal:3(<module>)
    100/2    0.002    0.000    0.704    0.352 <frozen importlib._bootstrap>:986(_find_and_load)
    100/2    0.002    0.000    0.703    0.352 <frozen importlib._bootstrap>:956(_find_and_load_unlocked)
     93/2    0.002    0.000    0.700    0.350 <frozen importlib._bootstrap>:650(_load_unlocked)
     67/1    0.001    0.000    0.699    0.699 <frozen importlib._bootstrap_external>:777(exec_module)
    115/1    0.000    0.000    0.698    0.698 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
        1    0.000    0.000    0.698    0.698 __init__.py:2(<module>)
    30/19    0.000    0.000    0.445    0.023 {built-in method builtins.__import__}
       82    0.002    0.000    0.372    0.005 re.py:289(_compile)
       75    0.000    0.000    0.369    0.005 re.py:250(compile)
       73    0.002    0.000    0.367    0.005 sre_compile.py:759(compile)
        1    0.000    0.000    0.262    0.262 requirements.py:4(<module>)
       73    0.001    0.000    0.259    0.004 sre_parse.py:937(parse)
   308/73    0.007    0.000    0.255    0.003 sre_parse.py:435(_parse_sub)
   457/77    0.072    0.000    0.253    0.003 sre_parse.py:493(_parse)
  283/280    0.009    0.000    0.222    0.001 {built-in method builtins.__build_class__}
      9/7    0.000    0.000    0.159    0.023 <frozen importlib._bootstrap>:613(_load_backward_compatible)
        6    0.000    0.000    0.159    0.026 __init__.py:35(load_module)
        1    0.001    0.001    0.145    0.145 pyparsing.py:26(<module>)
       26    0.001    0.000    0.137    0.005 pyparsing.py:2779(__init__)
       73    0.001    0.000    0.104    0.001 sre_compile.py:598(_code)
        1    0.000    0.000    0.097    0.097 parser.py:5(<module>)
        1    0.000    0.000    0.095    0.095 feedparser.py:5(<module>)
        1    0.000    0.000    0.095    0.095 specifiers.py:4(<module>)
   770/73    0.032    0.000    0.091    0.001 sre_compile.py:71(_compile)
        1    0.000    0.000    0.086    0.086 _policybase.py:1(<module>)
    11488    0.049    0.000    0.086    0.000 sre_parse.py:254(get)
        1    0.000    0.000    0.083    0.083 specifiers.py:275(Specifier)
        1    0.000    0.000    0.073    0.073 pyparsing.py:5399(pyparsing_common)
       98    0.003    0.000    0.063    0.001 <frozen importlib._bootstrap>:890(_find_spec)
        1    0.000    0.000    0.058    0.058 utils.py:5(<module>)

WITH PATCH:

         300 function calls (299 primitive calls) in 0.003 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      2/1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
        1    0.000    0.000    0.003    0.003 minimal:2(<module>)
        1    0.000    0.000    0.003    0.003 <frozen importlib._bootstrap>:986(_find_and_load)
        1    0.000    0.000    0.002    0.002 <frozen importlib._bootstrap>:956(_find_and_load_unlocked)
        1    0.000    0.000    0.002    0.002 <frozen importlib._bootstrap>:890(_find_spec)
        1    0.000    0.000    0.002    0.002 <frozen importlib._bootstrap_external>:1334(find_spec)
        1    0.000    0.000    0.002    0.002 <frozen importlib._bootstrap_external>:1302(_get_spec)
        2    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap_external>:1431(find_spec)
        1    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap>:650(_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1479(_fill_cache)
       18    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:62(_path_join)
        1    0.000    0.000    0.000    0.000 {built-in method posix.listdir}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:777(exec_module)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:849(get_code)
       11    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:90(_path_is_mode_type)
        9    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:99(_path_isfile)
       15    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:80(_path_stat)
       18    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:64(<listcomp>)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:549(module_from_spec)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:477(_init_module_attrs)
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1265(_path_importer_cache)
       15    0.000    0.000    0.000    0.000 {built-in method posix.stat}
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:294(cache_from_source)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1252(_path_hooks)
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:376(cached)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:424(_get_cached)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1520(path_hook_for_FileFinder)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:969(get_data)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:147(__enter__)
       38    0.000    0.000    0.000    0.000 {method 'rstrip' of 'str' objects}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1394(__init__)
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:104(_path_isdir)

Trevor Gamblin (1):
  python3-setuptools: patch entrypoints for performance boost

 .../python/python-setuptools.inc              |  4 +-
 ...nt-usr-bin-wrappers-signoff-included.patch | 60 +++++++++++++++++++
 2 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 meta/recipes-devtools/python/python3-setuptools/0001-ScriptWriter-create-more-efficient-usr-bin-wrappers-signoff-included.patch

-- 
2.24.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2020-06-23 23:02 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-06-23 22:58 [PATCH 0/1] Improving setuptools performance in Python Trevor Gamblin
2020-06-23 22:58 ` [PATCH] python3-setuptools: patch entrypoints for faster initialization Trevor Gamblin
2020-06-23 23:02 ` ✗ patchtest: failure for " Patchwork

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox