##// END OF EJS Templates
util: lower water mark when removing nodes after cost limit reached...
util: lower water mark when removing nodes after cost limit reached See the inline comment for the reasoning here. This is a pretty common strategy for garbage collectors, other cache-like primtives. The performance impact is substantial: $ hg perflrucachedict --size 4 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 100 ! inserts w/ cost limit ! wall 1.659181 comb 1.650000 user 1.650000 sys 0.000000 (best of 7) ! wall 1.722122 comb 1.720000 user 1.720000 sys 0.000000 (best of 6) ! mixed w/ cost limit ! wall 1.139955 comb 1.140000 user 1.140000 sys 0.000000 (best of 9) ! wall 1.182513 comb 1.180000 user 1.180000 sys 0.000000 (best of 9) $ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000 ! inserts ! wall 0.679546 comb 0.680000 user 0.680000 sys 0.000000 (best of 15) ! sets ! wall 0.825147 comb 0.830000 user 0.830000 sys 0.000000 (best of 13) ! inserts w/ cost limit ! wall 25.105273 comb 25.080000 user 25.080000 sys 0.000000 (best of 3) ! wall 1.724397 comb 1.720000 user 1.720000 sys 0.000000 (best of 6) ! mixed ! wall 0.807096 comb 0.810000 user 0.810000 sys 0.000000 (best of 13) ! mixed w/ cost limit ! wall 12.104470 comb 12.070000 user 12.070000 sys 0.000000 (best of 3) ! wall 1.190563 comb 1.190000 user 1.190000 sys 0.000000 (best of 9) $ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000 --mixedgetfreq 90 ! inserts ! wall 0.711177 comb 0.710000 user 0.710000 sys 0.000000 (best of 14) ! sets ! wall 0.846992 comb 0.850000 user 0.850000 sys 0.000000 (best of 12) ! inserts w/ cost limit ! wall 25.963028 comb 25.960000 user 25.960000 sys 0.000000 (best of 3) ! wall 2.184311 comb 2.180000 user 2.180000 sys 0.000000 (best of 5) ! mixed ! wall 0.728256 comb 0.730000 user 0.730000 sys 0.000000 (best of 14) ! mixed w/ cost limit ! wall 3.174256 comb 3.170000 user 3.170000 sys 0.000000 (best of 4) ! wall 0.773186 comb 0.770000 user 0.770000 sys 0.000000 (best of 13) $ hg perflrucachedict --size 100000 --gets 1000000 --sets 1000000 --mixed 1000000 --mixedgetfreq 90 --costlimit 5000000 ! gets ! wall 1.191368 comb 1.190000 user 1.190000 sys 0.000000 (best of 9) ! wall 1.195304 comb 1.190000 user 1.190000 sys 0.000000 (best of 9) ! inserts ! wall 0.950995 comb 0.950000 user 0.950000 sys 0.000000 (best of 11) ! inserts w/ cost limit ! wall 1.589732 comb 1.590000 user 1.590000 sys 0.000000 (best of 7) ! sets ! wall 1.094941 comb 1.100000 user 1.090000 sys 0.010000 (best of 9) ! mixed ! wall 0.936420 comb 0.940000 user 0.930000 sys 0.010000 (best of 10) ! mixed w/ cost limit ! wall 0.882780 comb 0.870000 user 0.870000 sys 0.000000 (best of 11) This puts us ~2x slower than caches without cost accounting. And for read-heavy workloads (the prime use cases for caches), performance is nearly identical. In the worst case (pure write workloads with cost accounting enabled), we're looking at ~1.5us per insert on large caches. That seems "fast enough." Differential Revision: https://phab.mercurial-scm.org/D4505

File last commit:

r38279:15a1e37f default
r39606:f296c0b3 default
Show More
profiling.py
251 lines | 7.9 KiB | text/x-python | PythonLexer
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 # profiling.py - profiling functions
#
# Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 import contextlib
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
from .i18n import _
from . import (
Pulkit Goyal
py3: replace pycompat.getenv with encoding.environ.get...
r30820 encoding,
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 error,
Jun Wu
profiling: allow loading profiling extension before everything else...
r32417 extensions,
Matt Harbison
profile: colorize output on Windows...
r36701 pycompat,
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 util,
)
Jun Wu
profiling: allow loading profiling extension before everything else...
r32417 def _loadprofiler(ui, profiler):
"""load profiler extension. return profile method, or None on failure"""
extname = profiler
extensions.loadall(ui, whitelist=[extname])
try:
mod = extensions.find(extname)
except KeyError:
return None
else:
return getattr(mod, 'profile', None)
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def lsprofile(ui, fp):
Jun Wu
codemod: register core configitems using a script...
r33499 format = ui.config('profiling', 'format')
field = ui.config('profiling', 'sort')
limit = ui.configint('profiling', 'limit')
climit = ui.configint('profiling', 'nested')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
if format not in ['text', 'kcachegrind']:
ui.warn(_("unrecognized profiling format '%s'"
" - Ignored\n") % format)
format = 'text'
try:
from . import lsprof
except ImportError:
raise error.Abort(_(
'lsprof not available - install from '
'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
p = lsprof.Profiler()
p.enable(subcalls=True)
try:
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
p.disable()
if format == 'kcachegrind':
from . import lsprofcalltree
calltree = lsprofcalltree.KCacheGrind(p)
calltree.output(fp)
else:
# format == 'text'
stats = lsprof.Stats(p.getstats())
stats.sort(field)
stats.pprint(limit=limit, file=fp, climit=climit)
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def flameprofile(ui, fp):
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
from flamegraph import flamegraph
except ImportError:
raise error.Abort(_(
'flamegraph not available - install from '
'https://github.com/evanhempel/python-flamegraph'))
# developer config: profiling.freq
Jun Wu
codemod: register core configitems using a script...
r33499 freq = ui.configint('profiling', 'freq')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 filter_ = None
collapse_recursion = True
thread = flamegraph.ProfileThread(fp, 1.0 / freq,
filter_, collapse_recursion)
Simon Farnsworth
mercurial: switch to util.timer for all interval timings...
r30975 start_time = util.timer()
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
thread.start()
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
thread.stop()
thread.join()
print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
Simon Farnsworth
mercurial: switch to util.timer for all interval timings...
r30975 util.timer() - start_time, thread.num_frames(),
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 thread.num_frames(unique=True)))
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def statprofile(ui, fp):
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 from . import statprof
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Jun Wu
codemod: register core configitems using a script...
r33499 freq = ui.configint('profiling', 'freq')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 if freq > 0:
Gregory Szorc
profiling: don't error with statprof when profiling has already started...
r29785 # Cannot reset when profiler is already active. So silently no-op.
if statprof.state.profile_level == 0:
statprof.reset(freq)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 else:
ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
Boris Feld
profiling: introduce a "profiling.time-track" option...
r38279 track = ui.config('profiling', 'time-track')
statprof.start(mechanism='thread', track=track)
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 data = statprof.stop()
Jun Wu
codemod: register core configitems using a script...
r33499 profformat = ui.config('profiling', 'statformat')
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316
formats = {
'byline': statprof.DisplayFormats.ByLine,
'bymethod': statprof.DisplayFormats.ByMethod,
'hotpath': statprof.DisplayFormats.Hotpath,
'json': statprof.DisplayFormats.Json,
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 'chrome': statprof.DisplayFormats.Chrome,
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 }
if profformat in formats:
displayformat = formats[profformat]
else:
ui.warn(_('unknown profiler output format: %s\n') % profformat)
displayformat = statprof.DisplayFormats.Hotpath
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 kwargs = {}
def fraction(s):
profiling: cope with configwith default value handling changes...
r32978 if isinstance(s, (float, int)):
return float(s)
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 if s.endswith('%'):
v = float(s[:-1]) / 100
else:
v = float(s)
if 0 <= v <= 1:
return v
raise ValueError(s)
if profformat == 'chrome':
showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
Boris Feld
configitems: register the 'profiling.showmax' config
r34411 showmax = ui.configwith(fraction, 'profiling', 'showmax')
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
Gregory Szorc
profiling: allow configuring minimum display threshold for hotpath...
r32851 elif profformat == 'hotpath':
Gregory Szorc
check-config: syntax to allow inconsistent config values...
r33192 # inconsistent config: profiling.showmin
Gregory Szorc
profiling: allow configuring minimum display threshold for hotpath...
r32851 limit = ui.configwith(fraction, 'profiling', 'showmin', 0.05)
Pulkit Goyal
py3: fix handling of keyword arguments at more places...
r36418 kwargs[r'limit'] = limit
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930
statprof.display(fp, data=data, format=displayformat, **kwargs)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: upgrade the "profile" context manager to a full class...
r32783 class profile(object):
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 """Start profiling.
Profiling is active when the context manager is active. When the context
manager exits, profiling results will be written to the configured output.
"""
profile: introduce a knob to control if the context is actually profiling...
r32785 def __init__(self, ui, enabled=True):
profile: upgrade the "profile" context manager to a full class...
r32783 self._ui = ui
self._output = None
self._fp = None
profile: use explicit logic to control file closing...
r32805 self._fpdoclose = True
profile: upgrade the "profile" context manager to a full class...
r32783 self._profiler = None
profile: introduce a knob to control if the context is actually profiling...
r32785 self._enabled = enabled
profile: introduce a "start" method to the profile context...
r32784 self._entered = False
self._started = False
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: upgrade the "profile" context manager to a full class...
r32783 def __enter__(self):
profile: introduce a "start" method to the profile context...
r32784 self._entered = True
profile: introduce a knob to control if the context is actually profiling...
r32785 if self._enabled:
self.start()
profile: make the contextmanager object available to the callers...
r32786 return self
profile: introduce a "start" method to the profile context...
r32784
def start(self):
"""Start profiling.
The profiling will stop at the context exit.
If the profiler was already started, this has no effect."""
if not self._entered:
raise error.ProgrammingError()
if self._started:
return
self._started = True
profile: upgrade the "profile" context manager to a full class...
r32783 profiler = encoding.environ.get('HGPROF')
proffn = None
if profiler is None:
Boris Feld
configitems: register the 'profiling.type' config
r34413 profiler = self._ui.config('profiling', 'type')
profile: upgrade the "profile" context manager to a full class...
r32783 if profiler not in ('ls', 'stat', 'flame'):
# try load profiler from extension with the same name
proffn = _loadprofiler(self._ui, profiler)
if proffn is None:
self._ui.warn(_("unrecognized profiler '%s' - ignored\n")
% profiler)
profiler = 'stat'
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: upgrade the "profile" context manager to a full class...
r32783 self._output = self._ui.config('profiling', 'output')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: close 'fp' on error within '__enter__'...
r32808 try:
profile: indent part of '__enter__'...
r32807 if self._output == 'blackbox':
self._fp = util.stringio()
elif self._output:
path = self._ui.expandpath(self._output)
self._fp = open(path, 'wb')
Matt Harbison
profile: colorize output on Windows...
r36701 elif pycompat.iswindows:
# parse escape sequence by win32print()
class uifp(object):
def __init__(self, ui):
self._ui = ui
def write(self, data):
self._ui.write_err(data)
def flush(self):
self._ui.flush()
self._fpdoclose = False
self._fp = uifp(self._ui)
profile: indent part of '__enter__'...
r32807 else:
self._fpdoclose = False
self._fp = self._ui.ferr
profile: upgrade the "profile" context manager to a full class...
r32783
profile: indent part of '__enter__'...
r32807 if proffn is not None:
pass
elif profiler == 'ls':
proffn = lsprofile
elif profiler == 'flame':
proffn = flameprofile
else:
proffn = statprofile
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783
profile: indent part of '__enter__'...
r32807 self._profiler = proffn(self._ui, self._fp)
self._profiler.__enter__()
profile: close 'fp' on error within '__enter__'...
r32808 except: # re-raises
self._closefp()
raise
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783
profile: upgrade the "profile" context manager to a full class...
r32783 def __exit__(self, exception_type, exception_value, traceback):
profile: properly propagate exception from the sub-context manager...
r32810 propagate = None
profile: close 'fp' in all cases...
r32809 if self._profiler is not None:
profile: properly propagate exception from the sub-context manager...
r32810 propagate = self._profiler.__exit__(exception_type, exception_value,
traceback)
profile: close 'fp' in all cases...
r32809 if self._output == 'blackbox':
val = 'Profile:\n%s' % self._fp.getvalue()
# ui.log treats the input as a format string,
# so we need to escape any % signs.
val = val.replace('%', '%%')
self._ui.log('profile', val)
profile: remove now useless indent...
r32806 self._closefp()
profile: properly propagate exception from the sub-context manager...
r32810 return propagate
profiling: move 'fp' closing logic into its own function...
r32804
def _closefp(self):
profile: use explicit logic to control file closing...
r32805 if self._fpdoclose and self._fp is not None:
self._fp.close()