Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: further speedup for 2.0 startup #2510

Merged
merged 11 commits into from
May 30, 2021
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