Skip to content

Commit

Permalink
perf: further speedup for 2.0 startup (#2510)
Browse files Browse the repository at this point in the history
* perf: remove the usage of pkg_resources

* perf: fastentrypoints to solve slow startup in 3.7
  • Loading branch information
hanxiao authored May 30, 2021
1 parent cc87cd5 commit fecdb41
Show file tree
Hide file tree
Showing 65 changed files with 287 additions and 306 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
!README.md
!MANIFEST.in
!cli/**
!daemon/**
!daemon/**
!fastentrypoints.py
17 changes: 9 additions & 8 deletions .github/2.0/1vs2.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ up to speed in no time.

Compared to 1.x (`1.4.2`), 2.0 is

- 220% faster on startup;
- 220% faster on `import`;
- 5% faster on data transmission.
- 3.6x faster on startup;
- 3.4x faster on `import`;
- 1.1x faster on data transmission.

<details>
<summary>Click to see benchmark</summary>
Expand All @@ -25,16 +25,16 @@ Compared to 1.x (`1.4.2`), 2.0 is
<table>
<tr>
<td>
<b>2.0.0rc1</b>
<b>2.0.0rc5</b>
</td>
</tr>
<tr>
<td>

```console
Benchmark #1: jina -v
Time (mean ± σ): 641.3 ms ± 4.7 ms [User: 575.8 ms, System: 823.0 ms]
Range (min … max): 635.5 ms … 650.4 ms 10 runs
Time (mean ± σ): 392.5 ms ± 3.6 ms [User: 278.6 ms, System: 385.4 ms]
Range (min … max): 386.3 ms … 397.0 ms 10 runs
```

</td>
Expand Down Expand Up @@ -69,9 +69,10 @@ Benchmark #1: jina -v
<td>

```console
hyperfine --warmup 1 'python -c "from jina import Document, Flow, Executor"'
Benchmark #1: python -c "from jina import Document, Flow, Executor"
Time (mean ± σ): 532.0 ms ± 6.5 ms [User: 431.3 ms, System: 543.4 ms]
Range (min … max): 522.7 ms … 544.0 ms 10 runs
Time (mean ± σ): 350.4 ms ± 6.1 ms [User: 265.7 ms, System: 398.7 ms]
Range (min … max): 343.2 ms … 363.2 ms 10 runs
```

</td>
Expand Down
3 changes: 2 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ area/testing:
- tests/**/*

area/setup:
- ./setup.py
- setup.py
- extra-requirements.txt
- requirements.txt
- MANIFEST.in
- fastentrypoints.py

area/core:
- jina/**/*
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
recursive-include jina/resources *
recursive-include jina/helloworld *
include extra-requirements.txt
include fastentrypoints.py
prune tests/
prune **/tests/
20 changes: 10 additions & 10 deletions cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import sys
import os


def _get_run_args(print_args: bool = True):
from jina.logging import default_logger
from jina.logging.predefined import default_logger
from jina.parsers import get_main_parser
from jina.helper import colored
from jina import __resources_path__

parser = get_main_parser()
if len(sys.argv) > 1:
from argparse import _StoreAction, _StoreTrueAction

args = parser.parse_args()
if print_args:
from pkg_resources import resource_filename

p = parser._actions[-1].choices[sys.argv[1]]
default_args = {
Expand All @@ -21,9 +22,7 @@ def _get_run_args(print_args: bool = True):
if isinstance(a, (_StoreAction, _StoreTrueAction))
}

with open(
resource_filename('jina', '/'.join(('resources', 'jina.logo')))
) as fp:
with open(os.path.join(__resources_path__, 'jina.logo')) as fp:
logo_str = fp.read()
param_str = []
for k, v in sorted(vars(args).items()):
Expand Down Expand Up @@ -67,9 +66,8 @@ def _is_latest_version(suppress_on_error=True):
try:
from urllib.request import Request, urlopen
import json
from pkg_resources import parse_version
from jina import __version__
from jina.logging import default_logger
from jina.logging.predefined import default_logger

req = Request(
'https://api.jina.ai/latest', headers={'User-Agent': 'Mozilla/5.0'}
Expand All @@ -78,8 +76,10 @@ def _is_latest_version(suppress_on_error=True):
req, timeout=1
) as resource: # 'with' is important to close the resource after use
latest_ver = json.load(resource)['version']
latest_ver = parse_version(latest_ver)
cur_ver = parse_version(__version__)
from distutils.version import LooseVersion

latest_ver = LooseVersion(latest_ver)
cur_ver = LooseVersion(__version__)
if cur_ver < latest_ver:
default_logger.warning(
f'WARNING: You are using Jina version {cur_ver}, however version {latest_ver} is available. '
Expand All @@ -99,5 +99,5 @@ def main():
from . import api

args = _get_run_args()
_is_latest_version()
# _is_latest_version()
getattr(api, args.cli.replace('-', '_'))(args)
4 changes: 2 additions & 2 deletions cli/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def export_api(args: 'Namespace'):
from .export import api_to_dict
from jina.jaml import JAML
from jina import __version__
from jina.logging import default_logger
from jina.logging.predefined import default_logger
from jina.schemas import get_full_schema

if args.yaml_path:
Expand Down Expand Up @@ -155,7 +155,7 @@ def flow(args: 'Namespace'):
with f:
f.block()
else:
from jina.logging import default_logger
from jina.logging.predefined import default_logger

default_logger.critical('start a flow from CLI requires a valid "--uses"')

Expand Down
10 changes: 5 additions & 5 deletions daemon/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import json
import subprocess
import threading

import pkg_resources
import os

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from uvicorn import Config, Server

from daemon.excepts import Runtime400Exception, daemon_runtime_exception_handler
from jina import __version__
from jina.logging import JinaLogger
from jina import __version__, __resources_path__
from jina.logging.logger import JinaLogger
from .parser import get_main_parser, _get_run_args

jinad_args = get_main_parser().parse_args([])
Expand Down Expand Up @@ -85,7 +85,7 @@ def _start_uvicorn(app: 'FastAPI'):

def _start_fluentd():
daemon_logger.info('starting fluentd...')
cfg = pkg_resources.resource_filename('jina', 'resources/fluent.conf')
cfg = os.path.join(__resources_path__, 'fluent.conf')
try:
fluentd_proc = subprocess.Popen(
['fluentd', '-c', cfg],
Expand Down
12 changes: 5 additions & 7 deletions daemon/parser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import os
import sys

from pkg_resources import resource_filename

from jina import __resources_path__
from jina.parsers.base import set_base_parser
from jina.parsers.helper import add_arg_group
from jina.parsers.peapods.base import mixin_base_ppr_parser
Expand Down Expand Up @@ -37,12 +36,14 @@ def get_main_parser():
mixin_base_ppr_parser(parser)
mixin_daemon_parser(parser)

from jina import __resources_path__

parser.set_defaults(
port_expose=8000,
workspace='/tmp/jinad',
log_config=os.getenv(
'JINAD_LOG_CONFIG',
resource_filename('jina', '/'.join(('resources', 'logging.daemon.yml'))),
os.path.join(__resources_path__, 'logging.daemon.yml'),
),
)

Expand All @@ -58,17 +59,14 @@ def _get_run_args(print_args: bool = True):

args = parser.parse_args()
if print_args:
from pkg_resources import resource_filename

default_args = {
a.dest: a.default
for a in parser._actions
if isinstance(a, (_StoreAction, _StoreTrueAction))
}

with open(
resource_filename('jina', '/'.join(('resources', 'jina.logo')))
) as fp:
with open(os.path.join(__resources_path__, 'jina.logo')) as fp:
logo_str = fp.read()
param_str = []
for k, v in sorted(vars(args).items()):
Expand Down
2 changes: 1 addition & 1 deletion daemon/stores/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Dict, Any, Union

from jina.helper import colored
from jina.logging import JinaLogger
from jina.logging.logger import JinaLogger
from .. import jinad_args


Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
'https://github.com/jina-ai/jina/commit/*',
'.github/*',
'extra-requirements.txt',
'../../101',
'fastentrypoints.py' '../../101',
'../../102',
'http://www.twinsun.com/tz/tz-link.htm', # Broken link from pytz library
'https://urllib3.readthedocs.io/en/latest/contrib.html#google-app-engine', # Broken link from urllib3 library
Expand Down
116 changes: 116 additions & 0 deletions fastentrypoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# noqa: D300,D400
# Copyright (c) 2016, Aaron Christianson
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
Monkey patch setuptools to write faster console_scripts with this format:
import sys
from mymodule import entry_function
sys.exit(entry_function())
This is better.
(c) 2016, Aaron Christianson
http://github.com/ninjaaron/fast-entry_points
'''
from setuptools.command import easy_install
import re

TEMPLATE = r'''
# -*- coding: utf-8 -*-
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
__requires__ = '{3}'
import re
import sys
from {0} import {1}
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit({2}())
'''.lstrip()


@classmethod
def get_args(cls, dist, header=None): # noqa: D205,D400
"""
Yield write_script() argument tuples for a distribution's
console_scripts and gui_scripts entry points.
"""
if header is None:
# pylint: disable=E1101
header = cls.get_header()
spec = str(dist.as_requirement())
for type_ in 'console', 'gui':
group = type_ + '_scripts'
for name, ep in dist.get_entry_map(group).items():
# ensure_safe_name
if re.search(r'[\\/]', name):
raise ValueError("Path separators not allowed in script names")
script_text = TEMPLATE.format(
ep.module_name, ep.attrs[0], '.'.join(ep.attrs), spec, group, name
)
# pylint: disable=E1101
args = cls._get_script_args(type_, name, header, script_text)
for res in args:
yield res


# pylint: disable=E1101
easy_install.ScriptWriter.get_args = get_args


def main():
import os
import re
import shutil
import sys

dests = sys.argv[1:] or ['.']
filename = re.sub(r'\.pyc$', '.py', __file__)

for dst in dests:
shutil.copy(filename, dst)
manifest_path = os.path.join(dst, 'MANIFEST.in')
setup_path = os.path.join(dst, 'setup.py')

# Insert the include statement to MANIFEST.in if not present
with open(manifest_path, 'a+') as manifest:
manifest.seek(0)
manifest_content = manifest.read()
if 'include fastentrypoints.py' not in manifest_content:
manifest.write(
('\n' if manifest_content else '') + 'include fastentrypoints.py'
)

# Insert the import statement to setup.py if not present
with open(setup_path, 'a+') as setup:
setup.seek(0)
setup_content = setup.read()
if 'import fastentrypoints' not in setup_content:
setup.seek(0)
setup.truncate()
setup.write('import fastentrypoints\n' + setup_content)
Loading

0 comments on commit fecdb41

Please sign in to comment.