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

Add support for lazy loading and imports of some expensive subpackages and modules to speed up Perun startup time #259

Merged
merged 6 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ repos:
rev: 24.8.0
hooks:
- id: black
types: [file, python]
types_or: [python, pyi]
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
recursive-include perun *.pyi
include perun/py.typed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this py.typed file? It is some stub? Can we add some comment why it is here and why it is empty? Can it contain like comment # Empty file needed for lazy_loading?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a requirement of PEP 561 and described also in mypy documentation. Packages that distribute both runtime and type stub files (.pyi files) need to contain a py.typed file as well to indicate support for type hints. The MANIFEST.in file is then needed for sdist distribution to include the .pyi and py.typed files. As the PEP does not specify what the py.typed files should contain and it is easy enough to find an explanation for the file online, I'd just keep it empty.

2 changes: 1 addition & 1 deletion docs/_static/templates/degradation_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""..."""

from perun.utils.structs import DegradationInfo
from perun.utils.structs.common_structs import DegradationInfo


def my_degradation_checker(baseline_profile, target_profile):
Expand Down
2 changes: 1 addition & 1 deletion docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Perun Commands
Collect Commands
----------------

.. click:: perun.cli:collect
.. click:: perun.cli_groups.collect_cli:collect
:prog: perun collect

.. _cli-collect-units-ref:
Expand Down
4 changes: 2 additions & 2 deletions docs/degradation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ just small requirements and have to `yield` the reports about degradation as a i
``DegradationInfo`` objects specified as follows:

.. currentmodule: perun.utils.structs
.. autoclass:: perun.utils.structs.DegradationInfo
.. autoclass:: perun.utils.structs.common_structs.DegradationInfo
:members:

.. autoclass:: perun.utils.structs.PerformanceChange
.. autoclass:: perun.utils.structs.common_structs.PerformanceChange
:members:

You can register your new performance change checker as follows:
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ perun_files = files(
'LICENSE',
'pyproject.toml',
'tox.ini',
'MANIFEST.in',
)

perun_dir = 'perun'
Expand Down
4 changes: 4 additions & 0 deletions perun/check/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@

Contains the actual methods in isolate modules, and then a factory module,
containing helper and generic stuff."""

import lazy_loader as lazy

__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
21 changes: 21 additions & 0 deletions perun/check/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from .detection_kit import (
create_filter_by_model as create_filter_by_model,
create_model_record as create_model_record,
get_filtered_best_models_of as get_filtered_best_models_of,
get_function_values as get_function_values,
general_detection as general_detection,
)
from .factory import (
pre_collect_profiles as pre_collect_profiles,
degradation_in_minor as degradation_in_minor,
degradation_in_history as degradation_in_history,
degradation_between_profiles as degradation_between_profiles,
run_degradation_check as run_degradation_check,
degradation_between_files as degradation_between_files,
is_rule_applicable_for as is_rule_applicable_for,
run_detection_with_strategy as run_detection_with_strategy,
)
from .nonparam_kit import (
classify_change as classify_change,
preprocess_nonparam_models as preprocess_nonparam_models,
)
2 changes: 1 addition & 1 deletion perun/check/detection_kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from perun.postprocess.regression_analysis import regression_models
from perun.profile import query
from perun.utils.common import common_kit
from perun.utils.structs import (
from perun.utils.structs.common_structs import (
PerformanceChange,
DegradationInfo,
ModelRecord,
Expand Down
30 changes: 1 addition & 29 deletions perun/check/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
polynomial_regression,
)
from perun.utils import decorators, log
from perun.utils.structs import (
from perun.utils.structs.common_structs import (
DetectionChangeResult,
DegradationInfo,
PerformanceChange,
Expand Down Expand Up @@ -61,34 +61,6 @@ def __call__(
"""Call Function"""


def get_supported_detection_models_strategies() -> list[str]:
"""
Provides supported detection models strategies to execute
the degradation check between two profiles with different kinds
of models. The individual strategies represent the way of
executing the detection between profiles and their models:

- best-param: best parametric models from both profiles
- best-non-param: best non-parametric models from both profiles
- best-model: best models from both profiles
- all-param: all parametric models pair from both profiles
- all-non-param: all non-parametric models pair from both profiles
- all-models: all models pair from both profiles
- best-both: best parametric and non-parametric models from both profiles

:return: the names of all supported degradation models strategies
"""
return [
"best-model",
"best-param",
"best-nonparam",
"all-param",
"all-nonparam",
"all-models",
"best-both",
]


def profiles_to_queue(
minor_version: str,
) -> dict[tuple[str, str, str, str], ProfileInfo]:
Expand Down
1 change: 1 addition & 0 deletions perun/check/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ perun_check_dir = perun_dir / 'check'

perun_check_files = files(
'__init__.py',
'__init__.pyi',
'factory.py',
'detection_kit.py',
'nonparam_kit.py',
Expand Down
2 changes: 1 addition & 1 deletion perun/check/methods/abstract_base_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# Perun Imports
if TYPE_CHECKING:
from perun.profile.factory import Profile
from perun.utils.structs import DegradationInfo
from perun.utils.structs.common_structs import DegradationInfo


class AbstractBaseChecker(ABC):
Expand Down
2 changes: 1 addition & 1 deletion perun/check/methods/average_amount_threshold.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.profile import convert
from perun.utils.common import common_kit
from perun.utils.structs import DegradationInfo, PerformanceChange
from perun.utils.structs.common_structs import DegradationInfo, PerformanceChange

if TYPE_CHECKING:
from perun.profile.factory import Profile
Expand Down
10 changes: 4 additions & 6 deletions perun/check/methods/best_model_order_equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
# Third-Party Imports

# Perun Imports
from perun import check as check
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.utils.structs import DegradationInfo, PerformanceChange
import perun.check.detection_kit as detection
from perun.utils.structs.common_structs import DegradationInfo, PerformanceChange

if TYPE_CHECKING:
from perun.profile.factory import Profile
Expand Down Expand Up @@ -81,10 +81,8 @@ def check(
:param _: unification with other detection methods (unused in this method)
:returns: tuple (degradation result, degradation location, degradation rate)
"""
best_baseline_models = detection.get_filtered_best_models_of(
baseline_profile, group="param"
)
best_target_models = detection.get_filtered_best_models_of(target_profile, group="param")
best_baseline_models = check.get_filtered_best_models_of(baseline_profile, group="param")
best_target_models = check.get_filtered_best_models_of(target_profile, group="param")

for uid, best_model in best_target_models.items():
best_baseline_model = best_baseline_models.get(uid)
Expand Down
2 changes: 1 addition & 1 deletion perun/check/methods/exclusive_time_outliers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.logic import config
from perun.profile import convert
from perun.utils.structs import DegradationInfo, PerformanceChange
from perun.utils.structs.common_structs import DegradationInfo, PerformanceChange

if TYPE_CHECKING:
from perun.profile.factory import Profile
Expand Down
6 changes: 3 additions & 3 deletions perun/check/methods/fast_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
import numpy as np

# Perun Imports
from perun import check as check
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.logic import runner
from perun.utils.structs import DegradationInfo, ClassificationMethod
import perun.check.detection_kit as detect
from perun.utils.structs.common_structs import DegradationInfo, ClassificationMethod

if TYPE_CHECKING:
from perun.profile.factory import Profile
Expand All @@ -35,7 +35,7 @@ def check(
:param _: unification with other detection methods (unused in this method)
:returns: tuple (degradation result, degradation location, degradation rate, confidence)
"""
return detect.general_detection(
return check.general_detection(
baseline_profile, target_profile, ClassificationMethod.FastCheck
)

Expand Down
10 changes: 5 additions & 5 deletions perun/check/methods/integral_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

# Perun Imports
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.check import factory, nonparam_kit as nparam_helpers
from perun import check as check
from perun.postprocess.regression_analysis import regression_models
from perun.utils.common import common_kit
from perun.utils.structs import DegradationInfo, ModelRecord, DetectionChangeResult
from perun.utils.structs.common_structs import DegradationInfo, ModelRecord, DetectionChangeResult

if TYPE_CHECKING:
from perun.profile.factory import Profile
Expand Down Expand Up @@ -95,7 +95,7 @@ def execute_analysis(
:return: tuple with degradation info between a pair of models:
(deg. result, deg. location, deg. rate, confidence type and rate, etc.)
"""
x_pts, baseline_y_pts, target_y_pts = nparam_helpers.preprocess_nonparam_models(
x_pts, baseline_y_pts, target_y_pts = check.preprocess_nonparam_models(
uid, baseline_model, target_profile, target_model
)

Expand All @@ -114,7 +114,7 @@ def execute_analysis(
float(target_integral - baseline_integral), float(baseline_integral)
)

change_info = nparam_helpers.classify_change(
change_info = check.classify_change(
rel_error if np.isfinite(rel_error) else 0,
_INTEGRATE_DIFF_NO_CHANGE,
_INTEGRATE_DIFF_CHANGE,
Expand Down Expand Up @@ -142,7 +142,7 @@ def check(
:param _: other kwgargs
:returns: tuple - degradation result (structure DegradationInfo)
"""
for degradation_info in factory.run_detection_with_strategy(
for degradation_info in check.run_detection_with_strategy(
execute_analysis, baseline_profile, target_profile, models_strategy
):
yield degradation_info
16 changes: 8 additions & 8 deletions perun/check/methods/linear_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
from scipy import stats

# Perun Imports
from perun.check import detection_kit as detect
from perun import check as check
from perun.check.methods import fast_check
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.utils.common import common_kit
from perun.utils.structs import DegradationInfo, ModelRecord, ClassificationMethod
from perun.utils.structs.common_structs import DegradationInfo, ModelRecord, ClassificationMethod

if TYPE_CHECKING:
import numpy
Expand All @@ -40,7 +40,7 @@ def check(
:returns: tuple (degradation result, degradation location, degradation rate, confidence)
"""

return detect.general_detection(
return check.general_detection(
baseline_profile, target_profile, ClassificationMethod.LinearRegression
)

Expand Down Expand Up @@ -107,15 +107,15 @@ def exec_linear_regression(
uid, baseline_profile, baseline_x_pts, lin_abs_error
)
# obtaining the models (linear and quadratic) from the new regressed profile
quad_err_model = detect.get_filtered_best_models_of(
quad_err_model = check.get_filtered_best_models_of(
std_err_profile,
group="param",
model_filter=detect.create_filter_by_model("quadratic"),
model_filter=check.create_filter_by_model("quadratic"),
)
linear_err_model = detect.get_filtered_best_models_of(
linear_err_model = check.get_filtered_best_models_of(
std_err_profile,
group="param",
model_filter=detect.create_filter_by_model("linear"),
model_filter=check.create_filter_by_model("linear"),
)

# check the last quadratic type of change
Expand All @@ -127,7 +127,7 @@ def exec_linear_regression(

# We did not classify the change
if not change_type:
std_err_model = detect.get_filtered_best_models_of(std_err_profile, group="param")
std_err_model = check.get_filtered_best_models_of(std_err_profile, group="param")
change_type = std_err_model[uid].type

return change_type
13 changes: 6 additions & 7 deletions perun/check/methods/local_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
from scipy import integrate

# Perun Imports
from perun.check import factory
from perun import check as check
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.profile.factory import Profile
from perun.utils.common import common_kit
from perun.utils.structs import DegradationInfo, ModelRecord, DetectionChangeResult
import perun.check.nonparam_kit as nparam_helpers
from perun.utils.structs.common_structs import DegradationInfo, ModelRecord, DetectionChangeResult

if TYPE_CHECKING:
import numpy.typing as npt
Expand Down Expand Up @@ -141,7 +140,7 @@ def classify_stats_diff(
"""
# create vectorized functions which take a np.arrays as inputs and perform actions over it
compare_diffs = np.vectorize(compare_diff_values)
classify_change = np.vectorize(nparam_helpers.classify_change)
classify_change = np.vectorize(check.classify_change)
stat_no = len(baseline_stats.keys())
stat_size = baseline_stats.get(list(baseline_stats.keys())[0])

Expand Down Expand Up @@ -227,7 +226,7 @@ def execute_analysis(
original_x_pts,
baseline_y_pts,
target_y_pts,
) = nparam_helpers.preprocess_nonparam_models(uid, baseline_model, target_profile, target_model)
) = check.preprocess_nonparam_models(uid, baseline_model, target_profile, target_model)

baseline_window_stats, _ = compute_window_stats(original_x_pts, baseline_y_pts)
target_window_stats, x_pts = compute_window_stats(original_x_pts, target_y_pts)
Expand All @@ -238,7 +237,7 @@ def execute_analysis(
x_pts_odd = x_pts[:, 1::2].reshape(-1, x_pts.size // 2)[0].round(2)
partial_intervals = list(np.array((change_info, partial_rel_error, x_pts_even, x_pts_odd)).T)

change_info_enum = nparam_helpers.classify_change(
change_info_enum = check.classify_change(
common_kit.safe_division(float(np.sum(partial_rel_error)), partial_rel_error.size),
_STATS_DIFF_NO_CHANGE,
_STATS_DIFF_CHANGE,
Expand Down Expand Up @@ -268,7 +267,7 @@ def check(
:param models_strategy: detection model strategy for obtains the relevant kind of models
:returns: tuple - degradation result
"""
for degradation_info in factory.run_detection_with_strategy(
for degradation_info in check.run_detection_with_strategy(
execute_analysis, baseline_profile, target_profile, models_strategy
):
yield degradation_info
6 changes: 3 additions & 3 deletions perun/check/methods/polynomial_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import numpy as np

# Perun Imports
from perun import check as check
from perun.check.methods.abstract_base_checker import AbstractBaseChecker
from perun.utils.structs import DegradationInfo, ClassificationMethod
import perun.check.detection_kit as detect
from perun.utils.structs.common_structs import DegradationInfo, ClassificationMethod

if TYPE_CHECKING:
import numpy.typing as npt
Expand All @@ -37,7 +37,7 @@ def check(
:param _: unification with other detection methods (unused in this method)
:returns: tuple (degradation result, degradation location, degradation rate, confidence)
"""
return detect.general_detection(
return check.general_detection(
baseline_profile,
target_profile,
ClassificationMethod.PolynomialRegression,
Expand Down
Loading
Loading