Skip to content

Commit 0b93497

Browse files
authored
Couple of small performance improvements (#552)
* Correctly use `functools.singledispatch`. Rather than checking `isinstance()` manually and then dispatching to a secondary function via `singledispatch` (_and_ having a backup function with thousands of pointless `isinstance()` branches), I've corrected all of the relevant uses to use `singledispatch` correctly since it's capable of using the object MRO to find the most appropriate implementation. * Update the Keyword intern cache to just use a simple `threading.Lock` instance rather than using an Atom, since the performance is much better.
1 parent db66a17 commit 0b93497

File tree

6 files changed

+92
-163
lines changed

6 files changed

+92
-163
lines changed

src/basilisp/lang/keyword.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import threading
12
from typing import Iterable, Optional
23

3-
from pyrsistent import PMap, pmap
4-
5-
import basilisp.lang.atom as atom
4+
import basilisp.lang.map as lmap
65
from basilisp.lang.interfaces import IAssociative, ILispObject
76

8-
__INTERN: atom.Atom["PMap[int, Keyword]"] = atom.Atom(pmap())
7+
_LOCK = threading.Lock()
8+
_INTERN: lmap.Map[int, "Keyword"] = lmap.Map.empty()
99

1010

1111
class Keyword(ILispObject):
@@ -48,46 +48,46 @@ def __reduce__(self):
4848

4949

5050
def complete(
51-
text: str, kw_cache: atom.Atom["PMap[int, Keyword]"] = __INTERN
51+
text: str, kw_cache: Optional[lmap.Map[int, Keyword]] = None,
5252
) -> Iterable[str]:
5353
"""Return an iterable of possible completions for the given text."""
5454
assert text.startswith(":")
55-
interns = kw_cache.deref()
5655
text = text[1:]
5756

57+
if kw_cache is None:
58+
kw_cache = _INTERN
59+
5860
if "/" in text:
5961
prefix, suffix = text.split("/", maxsplit=1)
6062
results = filter(
6163
lambda kw: (kw.ns is not None and kw.ns == prefix)
6264
and kw.name.startswith(suffix),
63-
interns.itervalues(),
65+
kw_cache.itervalues(),
6466
)
6567
else:
6668
results = filter(
6769
lambda kw: kw.name.startswith(text)
6870
or (kw.ns is not None and kw.ns.startswith(text)),
69-
interns.itervalues(),
71+
kw_cache.itervalues(),
7072
)
7173

7274
return map(str, results)
7375

7476

75-
def __get_or_create(
76-
kw_cache: "PMap[int, Keyword]", h: int, name: str, ns: Optional[str]
77-
) -> PMap:
78-
"""Private swap function used to either get the interned keyword
79-
instance from the input string."""
80-
if h in kw_cache:
81-
return kw_cache
82-
kw = Keyword(name, ns=ns)
83-
return kw_cache.set(h, kw)
77+
def keyword(name: str, ns: Optional[str] = None) -> Keyword:
78+
"""Create a new keyword with name and optional namespace.
8479
80+
Keywords are stored in a global cache by their hash. If a keyword with the same
81+
hash already exists in the cache, that keyword will be returned. If no keyword
82+
exists in the global cache, one will be created, entered into the cache, and then
83+
returned."""
84+
global _INTERN
8585

86-
def keyword(
87-
name: str,
88-
ns: Optional[str] = None,
89-
kw_cache: atom.Atom["PMap[int, Keyword]"] = __INTERN,
90-
) -> Keyword:
91-
"""Create a new keyword."""
9286
h = hash((name, ns))
93-
return kw_cache.swap(__get_or_create, h, name, ns)[h]
87+
with _LOCK:
88+
found = _INTERN.val_at(h)
89+
if found:
90+
return found
91+
kw = Keyword(name, ns=ns)
92+
_INTERN = _INTERN.assoc(h, kw)
93+
return kw

src/basilisp/lang/map.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ def keys(self):
7373
def values(self):
7474
return self._inner.values()
7575

76+
def iteritems(self):
77+
return self._inner.iteritems()
78+
79+
def iterkeys(self):
80+
return self._inner.iterkeys()
81+
82+
def itervalues(self):
83+
return self._inner.itervalues()
84+
7685
@property
7786
def meta(self) -> Optional[IPersistentMap]:
7887
return self._meta

src/basilisp/lang/obj.py

Lines changed: 26 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def seq_lrepr(
140140
return f"{start}{seq_lrepr}{end}"
141141

142142

143+
# pylint: disable=unused-argument
144+
@singledispatch
143145
def lrepr( # pylint: disable=too-many-arguments
144146
o: Any,
145147
human_readable: bool = False,
@@ -170,29 +172,11 @@ def lrepr( # pylint: disable=too-many-arguments
170172
runtime to the basilisp.core dynamic variables which correspond to each
171173
of the keyword arguments to this function. To use a version of lrepr
172174
which does capture those values, call basilisp.lang.runtime.lrepr directly."""
173-
if isinstance(o, LispObject):
174-
return o._lrepr(
175-
human_readable=human_readable,
176-
print_dup=print_dup,
177-
print_length=print_length,
178-
print_level=print_level,
179-
print_meta=print_meta,
180-
print_readably=print_readably,
181-
)
182-
else: # pragma: no cover
183-
return _lrepr_fallback(
184-
o,
185-
human_readable=human_readable,
186-
print_dup=print_dup,
187-
print_length=print_length,
188-
print_level=print_level,
189-
print_meta=print_meta,
190-
print_readably=print_readably,
191-
)
175+
return repr(o)
192176

193177

194-
@singledispatch
195-
def _lrepr_fallback( # pylint: disable=too-many-arguments
178+
@lrepr.register(LispObject)
179+
def _lrepr_lisp_obj( # pylint: disable=too-many-arguments
196180
o: Any,
197181
human_readable: bool = False,
198182
print_dup: bool = PRINT_DUP,
@@ -201,62 +185,27 @@ def _lrepr_fallback( # pylint: disable=too-many-arguments
201185
print_meta: bool = PRINT_META,
202186
print_readably: bool = PRINT_READABLY,
203187
) -> str: # pragma: no cover
204-
"""Fallback function for lrepr for subclasses of standard types.
205-
206-
The singledispatch used for standard lrepr dispatches using an exact
207-
type match on the first argument, so we will only hit this function
208-
for subclasses of common Python types like strings or lists."""
209-
kwargs = {
210-
"human_readable": human_readable,
211-
"print_dup": print_dup,
212-
"print_length": print_length,
213-
"print_level": print_level,
214-
"print_meta": print_meta,
215-
"print_readably": print_readably,
216-
}
217-
if isinstance(o, bool):
218-
return _lrepr_bool(o)
219-
elif o is None:
220-
return _lrepr_nil(o)
221-
elif isinstance(o, str):
222-
return _lrepr_str(
223-
o, human_readable=human_readable, print_readably=print_readably
224-
)
225-
elif isinstance(o, dict):
226-
return _lrepr_py_dict(o, **kwargs)
227-
elif isinstance(o, list):
228-
return _lrepr_py_list(o, **kwargs)
229-
elif isinstance(o, set):
230-
return _lrepr_py_set(o, **kwargs)
231-
elif isinstance(o, tuple):
232-
return _lrepr_py_tuple(o, **kwargs)
233-
elif isinstance(o, complex):
234-
return _lrepr_complex(o)
235-
elif isinstance(o, datetime.datetime):
236-
return _lrepr_datetime(o)
237-
elif isinstance(o, Decimal):
238-
return _lrepr_decimal(o, print_dup=print_dup)
239-
elif isinstance(o, Fraction):
240-
return _lrepr_fraction(o)
241-
elif isinstance(o, Pattern):
242-
return _lrepr_pattern(o)
243-
elif isinstance(o, uuid.UUID):
244-
return _lrepr_uuid(o)
245-
else:
246-
return repr(o)
188+
return o._lrepr(
189+
human_readable=human_readable,
190+
print_dup=print_dup,
191+
print_length=print_length,
192+
print_level=print_level,
193+
print_meta=print_meta,
194+
print_readably=print_readably,
195+
)
247196

248197

249-
@_lrepr_fallback.register(bool)
198+
@lrepr.register(bool)
250199
def _lrepr_bool(o: bool, **_) -> str:
251200
return repr(o).lower()
252201

253202

254-
@_lrepr_fallback.register(type(None))
203+
@lrepr.register(type(None))
255204
def _lrepr_nil(_: None, **__) -> str:
256205
return "nil"
257206

258207

259-
@_lrepr_fallback.register(str)
208+
@lrepr.register(str)
260209
def _lrepr_str(
261210
o: str, human_readable: bool = False, print_readably: bool = PRINT_READABLY, **_
262211
) -> str:
@@ -267,53 +216,53 @@ def _lrepr_str(
267216
return f'"{o.encode("unicode_escape").decode("utf-8")}"'
268217

269218

270-
@_lrepr_fallback.register(list)
219+
@lrepr.register(list)
271220
def _lrepr_py_list(o: list, **kwargs) -> str:
272221
return f"#py {seq_lrepr(o, '[', ']', **kwargs)}"
273222

274223

275-
@_lrepr_fallback.register(dict)
224+
@lrepr.register(dict)
276225
def _lrepr_py_dict(o: dict, **kwargs) -> str:
277226
return f"#py {map_lrepr(o.items, '{', '}', **kwargs)}"
278227

279228

280-
@_lrepr_fallback.register(set)
229+
@lrepr.register(set)
281230
def _lrepr_py_set(o: set, **kwargs) -> str:
282231
return f"#py {seq_lrepr(o, '#{', '}', **kwargs)}"
283232

284233

285-
@_lrepr_fallback.register(tuple)
234+
@lrepr.register(tuple)
286235
def _lrepr_py_tuple(o: tuple, **kwargs) -> str:
287236
return f"#py {seq_lrepr(o, '(', ')', **kwargs)}"
288237

289238

290-
@_lrepr_fallback.register(complex)
239+
@lrepr.register(complex)
291240
def _lrepr_complex(o: complex, **_) -> str:
292241
return repr(o).upper()
293242

294243

295-
@_lrepr_fallback.register(datetime.datetime)
244+
@lrepr.register(datetime.datetime)
296245
def _lrepr_datetime(o: datetime.datetime, **_) -> str:
297246
return f'#inst "{o.isoformat()}"'
298247

299248

300-
@_lrepr_fallback.register(Decimal)
249+
@lrepr.register(Decimal)
301250
def _lrepr_decimal(o: Decimal, print_dup: bool = PRINT_DUP, **_) -> str:
302251
if print_dup:
303252
return f"{str(o)}M"
304253
return str(o)
305254

306255

307-
@_lrepr_fallback.register(Fraction)
256+
@lrepr.register(Fraction)
308257
def _lrepr_fraction(o: Fraction, **_) -> str:
309258
return f"{o.numerator}/{o.denominator}"
310259

311260

312-
@_lrepr_fallback.register(type(re.compile("")))
261+
@lrepr.register(type(re.compile("")))
313262
def _lrepr_pattern(o: Pattern, **_) -> str:
314263
return f'#"{o.pattern}"'
315264

316265

317-
@_lrepr_fallback.register(uuid.UUID)
266+
@lrepr.register(uuid.UUID)
318267
def _lrepr_uuid(o: uuid.UUID, **_) -> str:
319268
return f'#uuid "{str(o)}"'

src/basilisp/lang/reader.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,22 +231,22 @@ def _py_from_lisp(
231231
raise SyntaxError(f"Unrecognized Python type: {type(form)}")
232232

233233

234-
@_py_from_lisp.register(llist.List)
234+
@_py_from_lisp.register(IPersistentList)
235235
def _py_tuple_from_list(form: llist.List) -> tuple:
236236
return tuple(form)
237237

238238

239-
@_py_from_lisp.register(lmap.Map)
239+
@_py_from_lisp.register(IPersistentMap)
240240
def _py_dict_from_map(form: lmap.Map) -> dict:
241241
return dict(form)
242242

243243

244-
@_py_from_lisp.register(lset.Set)
244+
@_py_from_lisp.register(IPersistentSet)
245245
def _py_set_from_set(form: lset.Set) -> set:
246246
return set(form)
247247

248248

249-
@_py_from_lisp.register(vector.Vector)
249+
@_py_from_lisp.register(IPersistentVector)
250250
def _py_list_from_vec(form: vector.Vector) -> list:
251251
return list(form)
252252

0 commit comments

Comments
 (0)