OpenCyphal / pydsdl

Cyphal DSDL processing front end implemented in Python
https://opencyphal.org
MIT License
11 stars 9 forks source link

Slow import #29

Closed pavel-kirienko closed 3 years ago

pavel-kirienko commented 4 years ago

The package is slow to import. This is bad because it affects the performance of CLI tools that rely on it, among other things. Import profiling (set -x PYTHONPROFILEIMPORTTIME 1) shows that most of the time is spent importing pydsdl._parser:

import time:       363 |        363 |                   pydsdl._error
import time:       440 |        440 |                           numbers
import time:       799 |       1239 |                         _decimal
import time:       189 |       1427 |                       decimal
import time:       237 |        237 |                       math
import time:      1711 |       3374 |                     fractions
import time:       199 |        199 |                     unicodedata
import time:       490 |       4062 |                   pydsdl._expression._primitive
import time:       377 |       4802 |                 pydsdl._expression._any
import time:       531 |        531 |                   pydsdl._expression._operator
import time:       581 |       1111 |                 pydsdl._expression._container
import time:       261 |       6173 |               pydsdl._expression
import time:       448 |        448 |               pydsdl._bit_length_set
import time:       342 |       6961 |             pydsdl._serializable._serializable
import time:       694 |        694 |             pydsdl._serializable._primitive
import time:       264 |        264 |             pydsdl._serializable._void
import time:       405 |        405 |             pydsdl._serializable._array
import time:       149 |        149 |               pydsdl._port_id_ranges
import time:       186 |        186 |                 pydsdl._serializable._name
import time:       327 |        512 |               pydsdl._serializable._attribute
import time:      1055 |       1716 |             pydsdl._serializable._composite
import time:       260 |      10297 |           pydsdl._serializable
import time:       212 |        212 |                     __future__
import time:      1397 |       1608 |                   six
import time:        85 |         85 |                       _ast
import time:       282 |        366 |                     ast
import time:       191 |        556 |                   parsimonious.utils
import time:       321 |       2485 |                 parsimonious.exceptions
import time:        47 |         47 |                     six.moves
import time:       387 |        387 |                     parsimonious.nodes
import time:       468 |        901 |                   parsimonious.expressions
import time:     15945 |      16845 |                 parsimonious.grammar
import time:       194 |      19524 |               parsimonious
import time:     37296 |      56819 |             pydsdl._parser
import time:       409 |      57228 |           pydsdl._dsdl_definition
import time:       805 |      71175 |         pydsdl._namespace
import time:       436 |      71610 |       pydsdl

Without further analysis I predict that the culprit is right here:

https://github.com/UAVCAN/pydsdl/blob/cb35ad3e8c0886d44facdfac14489b9ba7999d44/pydsdl/_parser.py#L140-L142

We parse the grammar definition at the time of package initialization. This is not great. Consider implementing lazy initialization:

@functools.lru_cache(None)
def _get_grammar() -> parsimonious.Grammar:
    ...