Skip to content

Commit 4ccb9c4

Browse files
committed
Improve performance by caching find_spec
Certain checkers upstream on pylint like import-error heavily use find_spec. This method is IO intensive as it looks for files across several search paths to return a ModuleSpec. Since imports across files may repeat themselves it makes sense to cache this method in order to speed up the linting process. Local testing shows that caching reduces the total amount of calls to find_module methods (used by find_spec) by about 50%. Linting the test repository in the related issue goes from 40 seconds to 37 seconds. This was on a NVME disk and after warmup, so timing gains may be bigger on slower file systems like the one mentioned in the referenced issue. Closes pylint-dev/pylint#9310.
1 parent 7a3b482 commit 4ccb9c4

File tree

3 files changed

+16
-0
lines changed

3 files changed

+16
-0
lines changed

astroid/interpreter/_import/spec.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from . import util
2626

27+
modpath_cache = {}
28+
2729

2830
# The MetaPathFinder protocol comes from typeshed, which says:
2931
# Intentionally omits one deprecated and one optional method of `importlib.abc.MetaPathFinder`
@@ -423,6 +425,18 @@ def _find_spec_with_path(
423425
raise ImportError(f"No module named {'.'.join(module_parts)}")
424426

425427

428+
def cache_modpath(func):
429+
def wrapper(*args):
430+
key = ".".join(args[0])
431+
if key not in modpath_cache:
432+
modpath_cache[key] = func(*args)
433+
434+
return modpath_cache[key]
435+
436+
return wrapper
437+
438+
439+
@cache_modpath
426440
def find_spec(modpath: list[str], path: Sequence[str] | None = None) -> ModuleSpec:
427441
"""Find a spec for the given module.
428442

tests/test_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class AstroidManagerTest(
4141
):
4242
def setUp(self) -> None:
4343
super().setUp()
44+
astroid.interpreter._import.spec.modpath_cache.clear()
4445
self.manager = test_utils.brainless_manager()
4546

4647
def test_ast_from_file(self) -> None:

tests/test_modutils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ModuleFileTest(unittest.TestCase):
4141
package = "mypypa"
4242

4343
def tearDown(self) -> None:
44+
astroid.interpreter._import.spec.modpath_cache.clear()
4445
for k in list(sys.path_importer_cache):
4546
if "MyPyPa" in k:
4647
del sys.path_importer_cache[k]

0 commit comments

Comments
 (0)