Skip to content

Commit 3f3bd84

Browse files
committed
perf(robot): use file_id tuples instead of os.path.samefile for path comparison
Replace repeated os.path.samefile() calls (2x stat per call) in namespace import loops with pre-computed (st_dev, st_ino) tuple comparisons. - Add FileId type, file_id() and same_file_id() to core utils - Add lazy source_id property to LibraryDoc (excluded from pickle) - Pre-compute source_id on Namespace init - Compute file_id once per find_resource result instead of per-iteration
1 parent 784632d commit 3f3bd84

File tree

3 files changed

+51
-17
lines changed

3 files changed

+51
-17
lines changed

packages/core/src/robotcode/core/utils/path.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
import sys
44
from pathlib import Path
5-
from typing import Any, Union
5+
from typing import Any, Optional, Tuple, Union
66

77

88
def path_is_relative_to(
@@ -61,8 +61,30 @@ def normalized_path_full(path: Union[str, "os.PathLike[str]"]) -> Path:
6161
return Path(*parents)
6262

6363

64+
FileId = Tuple[int, int]
65+
66+
67+
def file_id(path: Union[str, "os.PathLike[str]", Path]) -> Optional[FileId]:
68+
"""Return the (st_dev, st_ino) tuple for *path*, or ``None`` on error."""
69+
try:
70+
st = os.stat(path)
71+
return (st.st_dev, st.st_ino)
72+
except OSError:
73+
return None
74+
75+
6476
def same_file(path1: Union[str, "os.PathLike[str]", Path], path2: Union[str, "os.PathLike[str]", Path]) -> bool:
6577
try:
6678
return os.path.samefile(path1, path2)
6779
except OSError:
6880
return False
81+
82+
83+
def same_file_id(
84+
id1: Optional[FileId],
85+
id2: Optional[FileId],
86+
) -> bool:
87+
"""Compare two :func:`file_id` values. ``None`` never matches anything."""
88+
if id1 is None or id2 is None:
89+
return False
90+
return id1 == id2

packages/robot/src/robotcode/robot/diagnostics/library_doc.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
from robot.variables.finders import VariableFinder
6363
from robot.variables.replacer import VariableReplacer
6464
from robotcode.core.lsp.types import Position, Range
65-
from robotcode.core.utils.path import normalized_path
65+
from robotcode.core.utils.path import FileId, file_id, normalized_path
6666

6767
from ..utils import get_robot_version
6868
from ..utils.ast import (
@@ -1023,6 +1023,13 @@ class LibraryDoc:
10231023
stdout: Optional[str] = field(default=None, compare=False)
10241024
has_listener: Optional[bool] = None
10251025
library_type: Optional[LibraryType] = None
1026+
_source_id: Optional[FileId] = field(default=None, init=False, compare=False, hash=False, repr=False)
1027+
1028+
@property
1029+
def source_id(self) -> Optional[FileId]:
1030+
if self._source_id is None and self.source is not None:
1031+
self._source_id = file_id(self.source)
1032+
return self._source_id
10261033

10271034
@property
10281035
def inits(self) -> KeywordStore:
@@ -1053,6 +1060,17 @@ def __post_init__(self) -> None:
10531060
self._update_keywords(self._inits)
10541061
self._update_keywords(self._keywords)
10551062

1063+
def _all_slots(self) -> Iterator[str]:
1064+
for cls in type(self).__mro__:
1065+
yield from getattr(cls, "__slots__", ())
1066+
1067+
def __getstate__(self) -> Dict[str, Any]:
1068+
return {slot: getattr(self, slot) for slot in self._all_slots() if slot != "_source_id"}
1069+
1070+
def __setstate__(self, state: Dict[str, Any]) -> None:
1071+
for slot in self._all_slots():
1072+
object.__setattr__(self, slot, state.get(slot, None))
1073+
10561074
def __hash__(self) -> int:
10571075
return hash(
10581076
(

packages/robot/src/robotcode/robot/diagnostics/namespace.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from robotcode.core.text_document import TextDocument
4545
from robotcode.core.uri import Uri
4646
from robotcode.core.utils.logging import LoggingDescriptor
47-
from robotcode.core.utils.path import same_file
47+
from robotcode.core.utils.path import file_id, same_file_id
4848

4949
from ..utils import get_robot_version
5050
from ..utils.ast import (
@@ -713,6 +713,7 @@ def __init__(
713713

714714
self.model = model
715715
self.source = source
716+
self.source_id = file_id(source)
716717
self._document = weakref.ref(document) if document is not None else None
717718
self.document_type: Optional[DocumentType] = document_type
718719
self.languages = languages
@@ -1296,12 +1297,13 @@ def _import(
12961297
raise NameSpaceError("Resource setting requires value.")
12971298

12981299
source = self.imports_manager.find_resource(value.name, base_dir, variables=variables)
1300+
source_fid = file_id(source)
12991301

13001302
allread_imported_resource = next(
13011303
(
13021304
v
13031305
for k, v in self._resources.items()
1304-
if v.library_doc.source is not None and same_file(v.library_doc.source, source)
1306+
if v.library_doc.source_id is not None and same_file_id(v.library_doc.source_id, source_fid)
13051307
),
13061308
None,
13071309
)
@@ -1330,7 +1332,7 @@ def _import(
13301332
)
13311333
return None
13321334

1333-
if same_file(self.source, source):
1335+
if same_file_id(self.source_id, source_fid):
13341336
if parent_import:
13351337
self.append_diagnostics(
13361338
range=parent_import.range,
@@ -1609,12 +1611,8 @@ def _import_imports(
16091611
e
16101612
for e in self._variables_imports.values()
16111613
if (
1612-
(
1613-
e.library_doc.source is not None
1614-
and entry.library_doc.source is not None
1615-
and same_file(e.library_doc.source, entry.library_doc.source)
1616-
)
1617-
or (e.library_doc.source is None and entry.library_doc.source is None)
1614+
same_file_id(e.library_doc.source_id, entry.library_doc.source_id)
1615+
or (e.library_doc.source_id is None and entry.library_doc.source_id is None)
16181616
)
16191617
and e.alias == entry.alias
16201618
and e.args == entry.args
@@ -1681,12 +1679,8 @@ def _import_imports(
16811679
e
16821680
for e in self._libraries.values()
16831681
if (
1684-
(
1685-
e.library_doc.source is not None
1686-
and entry.library_doc.source is not None
1687-
and same_file(e.library_doc.source, entry.library_doc.source)
1688-
)
1689-
or (e.library_doc.source is None and entry.library_doc.source is None)
1682+
same_file_id(e.library_doc.source_id, entry.library_doc.source_id)
1683+
or (e.library_doc.source_id is None and entry.library_doc.source_id is None)
16901684
)
16911685
and e.library_doc.member_name == entry.library_doc.member_name
16921686
and e.alias == entry.alias

0 commit comments

Comments
 (0)