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

PR: Improve initialization time of "Signal", a core component of "Colour". #1057

Merged
merged 4 commits into from Oct 27, 2022
Merged

PR: Improve initialization time of "Signal", a core component of "Colour". #1057

merged 4 commits into from Oct 27, 2022

Conversation

tjdcs
Copy link
Contributor

@tjdcs tjdcs commented Oct 24, 2022

Summary

This PR implements a performance improvement for Signal in signal.py.

The initialization of _function, the private backing attribute for the function property, is relatively expensive. This performance issue was discovered when creating an MSDS object from a long list of SpectralDistributions or large ndarrays. The _create_function is invoked frequently when not needed. This PR updates relavent code to always use the function property. the function property has been changed to only invoke _create_function if the _function property has been invalidated (by setting it equal to None). If the function property is never accessed, then the relevant initialization does not take place (lazy initialization)

Under some conditions, this change results in a 90% reduction in Signal initialization run time

Preflight

Code Style and Quality

  • Unit tests have been implemented and passed.
  • Mypy static checking has been run and passed.
  • Pre-commit hooks have been run and passed.
  • [N/A] New transformations have been added to the Automatic Colour Conversion Graph.
  • [N/A] New transformations have been exported to the relevant namespaces, e.g. colour, colour.models.

Documentation

  • [N/A] New features are documented along with examples if relevant.
  • [N/A] The documentation is Sphinx and numpydoc compliant.

@tjdcs tjdcs marked this pull request as ready for review October 24, 2022 16:26
@tjdcs
Copy link
Contributor Author

tjdcs commented Oct 24, 2022

Looks like this is well formed now.

@tjdcs tjdcs changed the title Lazy initialize _function (relatively expensive) Improve initialization time of Signal, a core component of colour Oct 24, 2022
@tjdcs
Copy link
Contributor Author

tjdcs commented Oct 24, 2022

@KelSolaar Other than the issue with static type checking (looks like something related to a new version of pip and an old installation method from a dependency) this should be ready for review.

@KelSolaar KelSolaar changed the title Improve initialization time of Signal, a core component of colour PR: Improve initialization time of Signal, a core component of colour Oct 25, 2022
@KelSolaar KelSolaar changed the title PR: Improve initialization time of Signal, a core component of colour PR: Improve initialization time of "Signal", a core component of "Colour". Oct 25, 2022
@KelSolaar
Copy link
Member

KelSolaar commented Oct 25, 2022

Hey @tjdcs,

Thank you! Two notes:

  • The Static Typing issue is because we need to cast the return value of the function method to a Callable:
    @property
    def function(self) -> Callable:
        """
        Getter property for the continuous signal callable.

        Returns
        -------
        Callable
            Continuous signal callable.
        """

        if self._function is None:
            self._create_function()

        return cast(Callable, self._function)
  • Given that the _create_function is only used once now, we should probably move its code inside the function method directly, so maybe it becomes that now:
    @property
    def function(self) -> Callable:
        """
        Getter property for the continuous signal callable.

        Returns
        -------
        Callable
            Continuous signal callable.
        """

        if self._function is None:
            if self._domain.size != 0 and self._range.size != 0:
                self._function = self._extrapolator(
                    self._interpolator(
                        self.domain, self.range, **self._interpolator_kwargs
                    ),
                    **self._extrapolator_kwargs,
                )
            else:

                def _undefined_function(*args: Any, **kwargs: Any):
                    """
                    Raise a :class:`ValueError` exception.

                    Other Parameters
                    ----------------
                    args
                        Arguments.
                    kwargs
                        Keywords arguments.

                    Raises
                    ------
                    ValueError
                    """

                    raise ValueError(
                        "Underlying signal interpolator function does not exists, "
                        "please ensure you defined both "
                        '"domain" and "range" variables!'
                    )

                self._function = _undefined_function

        return cast(Callable, self._function)

@KelSolaar
Copy link
Member

Thank you!

There are some issues with GH so I cannot comment on the changes directly, anyway, the Callable | None construct was introduced in Python 3.10 so we tend to use the longer Union[Callable, None] form for now. I will merge and fix in a subsequent commit!

@KelSolaar KelSolaar merged commit 9e8fbd2 into colour-science:develop Oct 27, 2022
@KelSolaar KelSolaar added this to the v0.4.2 milestone Oct 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants