Mercurial > hg
view hgdemandimport/demandimportpy3.py @ 44363:f7459da77f23
nodemap: introduce an option to use mmap to read the nodemap mapping
The performance and memory benefit is much greater if we don't have to copy all
the data in memory for each information. So we introduce an option (on by
default) to read the data using mmap.
This changeset is the last one definition the API for index support nodemap
data. (they have to be able to use the mmaping).
Below are some benchmark comparing the best we currently have in 5.3 with the
final step of this series (using the persistent nodemap implementation in
Rust). The benchmark run `hg perfindex` with various revset and the following
variants:
Before:
* do not use the persistent nodemap
* use the CPython implementation of the index for nodemap
* use mmapping of the changelog index
After:
* use the MixedIndex Rust code, with the NodeTree object for nodemap access
(still in review)
* use the persistent nodemap data from disk
* access the persistent nodemap data through mmap
* use mmapping of the changelog index
The persistent nodemap greatly speed up most operation on very large
repositories. Some of the previously very fast lookup end up a bit slower because
the persistent nodemap has to be setup. However the absolute slowdown is very
small and won't matters in the big picture.
Here are some numbers (in seconds) for the reference copy of mozilla-try:
Revset Before After abs-change speedup
-10000: 0.004622 0.005532 0.000910 × 0.83
-10: 0.000050 0.000132 0.000082 × 0.37
tip 0.000052 0.000085 0.000033 × 0.61
0 + (-10000:) 0.028222 0.005337 -0.022885 × 5.29
0 0.023521 0.000084 -0.023437 × 280.01
(-10000:) + 0 0.235539 0.005308 -0.230231 × 44.37
(-10:) + :9 0.232883 0.000180 -0.232703 ×1293.79
(-10000:) + (:99) 0.238735 0.005358 -0.233377 × 44.55
:99 + (-10000:) 0.317942 0.005593 -0.312349 × 56.84
:9 + (-10:) 0.313372 0.000179 -0.313193 ×1750.68
:9 0.316450 0.000143 -0.316307 ×2212.93
On smaller repositories, the cost of nodemap related operation is not as big, so
the win is much more modest. Yet it helps shaving a handful of millisecond here
and there.
Here are some numbers (in seconds) for the reference copy of mercurial:
Revset Before After abs-change speedup
-10: 0.000065 0.000097 0.000032 × 0.67
tip 0.000063 0.000078 0.000015 × 0.80
0 0.000561 0.000079 -0.000482 × 7.10
-10000: 0.004609 0.003648 -0.000961 × 1.26
0 + (-10000:) 0.005023 0.003715 -0.001307 × 1.35
(-10:) + :9 0.002187 0.000108 -0.002079 ×20.25
(-10000:) + 0 0.006252 0.003716 -0.002536 × 1.68
(-10000:) + (:99) 0.006367 0.003707 -0.002660 × 1.71
:9 + (-10:) 0.003846 0.000110 -0.003736 ×34.96
:9 0.003854 0.000099 -0.003755 ×38.92
:99 + (-10000:) 0.007644 0.003778 -0.003866 × 2.02
Differential Revision: https://phab.mercurial-scm.org/D7894
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 11 Feb 2020 11:18:52 +0100 |
parents | f81c17ec303c |
children | a6e12d477595 |
line wrap: on
line source
# demandimportpy3 - global demand-loading of modules for Mercurial # # Copyright 2017 Facebook Inc. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """Lazy loading for Python 3.6 and above. This uses the new importlib finder/loader functionality available in Python 3.5 and up. The code reuses most of the mechanics implemented inside importlib.util, but with a few additions: * Allow excluding certain modules from lazy imports. * Expose an interface that's substantially the same as demandimport for Python 2. This also has some limitations compared to the Python 2 implementation: * Much of the logic is per-package, not per-module, so any packages loaded before demandimport is enabled will not be lazily imported in the future. In practice, we only expect builtins to be loaded before demandimport is enabled. """ # This line is unnecessary, but it satisfies test-check-py3-compat.t. from __future__ import absolute_import import contextlib import importlib.util import sys from . import tracing _deactivated = False # Python 3.5's LazyLoader doesn't work for some reason. # https://bugs.python.org/issue26186 is a known issue with extension # importing. But it appears to not have a meaningful effect with # Mercurial. _supported = sys.version_info[0:2] >= (3, 6) class _lazyloaderex(importlib.util.LazyLoader): """This is a LazyLoader except it also follows the _deactivated global and the ignore list. """ def exec_module(self, module): """Make the module load lazily.""" with tracing.log('demandimport %s', module): if _deactivated or module.__name__ in ignores: self.loader.exec_module(module) else: super().exec_module(module) class LazyFinder(object): """A wrapper around a ``MetaPathFinder`` that makes loaders lazy. ``sys.meta_path`` finders have their ``find_spec()`` called to locate a module. This returns a ``ModuleSpec`` if found or ``None``. The ``ModuleSpec`` has a ``loader`` attribute, which is called to actually load a module. Our class wraps an existing finder and overloads its ``find_spec()`` to replace the ``loader`` with our lazy loader proxy. We have to use __getattribute__ to proxy the instance because some meta path finders don't support monkeypatching. """ __slots__ = ("_finder",) def __init__(self, finder): object.__setattr__(self, "_finder", finder) def __repr__(self): return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder") # __bool__ is canonical Python 3. But check-code insists on __nonzero__ being # defined via `def`. def __nonzero__(self): return bool(object.__getattribute__(self, "_finder")) __bool__ = __nonzero__ def __getattribute__(self, name): if name in ("_finder", "find_spec"): return object.__getattribute__(self, name) return getattr(object.__getattribute__(self, "_finder"), name) def __delattr__(self, name): return delattr(object.__getattribute__(self, "_finder")) def __setattr__(self, name, value): return setattr(object.__getattribute__(self, "_finder"), name, value) def find_spec(self, *args, **kwargs): finder = object.__getattribute__(self, "_finder") spec = finder.find_spec(*args, **kwargs) # Lazy loader requires exec_module(). if ( spec is not None and spec.loader is not None and getattr(spec.loader, "exec_module") ): spec.loader = _lazyloaderex(spec.loader) return spec ignores = set() def init(ignoreset): global ignores ignores = ignoreset def isenabled(): return not _deactivated and any( isinstance(finder, LazyFinder) for finder in sys.meta_path ) def disable(): new_finders = [] for finder in sys.meta_path: new_finders.append( finder._finder if isinstance(finder, LazyFinder) else finder ) sys.meta_path[:] = new_finders def enable(): if not _supported: return new_finders = [] for finder in sys.meta_path: new_finders.append( LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder ) sys.meta_path[:] = new_finders @contextlib.contextmanager def deactivated(): # This implementation is a bit different from Python 2's. Python 3 # maintains a per-package finder cache in sys.path_importer_cache (see # PEP 302). This means that we can't just call disable + enable. # If we do that, in situations like: # # demandimport.enable() # ... # from foo.bar import mod1 # with demandimport.deactivated(): # from foo.bar import mod2 # # mod2 will be imported lazily. (The converse also holds -- whatever finder # first gets cached will be used.) # # Instead, have a global flag the LazyLoader can use. global _deactivated demandenabled = isenabled() if demandenabled: _deactivated = True try: yield finally: if demandenabled: _deactivated = False