Skip to content

Commit

Permalink
Minor optimizations in hot codepaths accessing class parameters (#893)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Jan 11, 2024
1 parent 937b021 commit 4d2f23c
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 37 deletions.
24 changes: 22 additions & 2 deletions benchmarks/benchmarks/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,32 @@ class P1(param.Parameterized):
self.P1 = P1
self.p1 = P1()

def test_class(self):
def time_class(self):
self.P1.param

def test_instance(self):
def time_instance(self):
self.p1.param

class ParameterizedParamContainsSuite:

def setup(self):
class P1(param.Parameterized):
x0 = param.Parameter()
x1 = param.Parameter()
x2 = param.Parameter()
x3 = param.Parameter()
x4 = param.Parameter()
x5 = param.Parameter()

self.P1 = P1
self.p1 = P1()

def time_class(self):
'x5' in self.P1.param

def time_instance(self):
'x5' in self.p1.param


class ParameterizedSetattrSuite:

Expand Down
73 changes: 38 additions & 35 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -1849,24 +1849,25 @@ def __getitem__(self_, key):
Returns the class or instance parameter
"""
inst = self_.self
params = self_ if inst is None else inst.param
p = params.objects(False)[key]
return p if inst is None else _instantiated_parameter(inst, p)
if inst is None:
return self_._cls_parameters[key]
p = self_.objects(instance=False)[key]
return _instantiated_parameter(inst, p)

def __dir__(self_):
"""
Adds parameters to dir
"""
return super().__dir__() + list(self_)
return super().__dir__() + list(self_._cls_parameters)

def __iter__(self_):
"""
Iterates over the parameters on this object.
"""
yield from self_.objects(instance=False)
yield from self_._cls_parameters

def __contains__(self_, param):
return param in list(self_)
return param in self_._cls_parameters

def __getattr__(self_, attr):
"""
Expand All @@ -1876,12 +1877,7 @@ def __getattr__(self_, attr):
if cls is None: # Class not initialized
raise AttributeError

params = list(cls._param__private.params)
if not params:
params = [n for class_ in classlist(cls) for n, v in class_.__dict__.items()
if isinstance(v, Parameter)]

if attr in params:
if attr in self_._cls_parameters:
return self_.__getitem__(attr)
elif self_.self is None:
raise AttributeError(f"type object '{self_.cls.__name__}.param' has no attribute {attr!r}")
Expand Down Expand Up @@ -1914,7 +1910,8 @@ def _setup_params(self_, **params):
## Deepcopy all 'instantiate=True' parameters
params_to_deepcopy = {}
params_to_ref = {}
for pname, p in self_.objects(instance=False).items():
objects = self_._cls_parameters
for pname, p in objects.items():
if p.instantiate and pname != "name":
params_to_deepcopy[pname] = p
elif p.constant and pname != 'name':
Expand All @@ -1927,7 +1924,6 @@ def _setup_params(self_, **params):

## keyword arg setting
deps, refs = {}, {}
objects = self.param.objects(instance=False)
for name, val in params.items():
desc = self_.cls.get_param_descriptor(name)[0] # pylint: disable-msg=E1101
if not desc:
Expand Down Expand Up @@ -2235,8 +2231,8 @@ def add_parameter(self_, param_name, param_obj):
# would need to handle the params() cache as well
# (which is tricky but important for startup speed).
cls = self_.cls
type.__setattr__(cls,param_name,param_obj)
ParameterizedMetaclass._initialize_parameter(cls,param_name,param_obj)
type.__setattr__(cls, param_name, param_obj)
ParameterizedMetaclass._initialize_parameter(cls, param_name, param_obj)
# delete cached params()
cls._param__private.params.clear()

Expand Down Expand Up @@ -2352,6 +2348,31 @@ def set_param(self_, *args,**kwargs):
(self_or_cls.name))
return self_.update(kwargs)

@property
def _cls_parameters(self_):
"""
Class parameters are cached because they are accessed often,
and parameters are rarely added (and cannot be deleted)
"""
cls = self_.cls
pdict = cls._param__private.params
if pdict:
return pdict

paramdict = {}
for class_ in classlist(cls):
for name, val in class_.__dict__.items():
if isinstance(val, Parameter):
paramdict[name] = val

# We only want the cache to be visible to the cls on which
# params() is called, so we mangle the name ourselves at
# runtime (if we were to mangle it now, it would be
# _Parameterized.__params for all classes).
# cls._param__private.params[f'_{cls.__name__}__params'] = paramdict
cls._param__private.params = paramdict
return paramdict

def objects(self_, instance=True):
"""
Returns the Parameters of this instance or class
Expand All @@ -2376,25 +2397,7 @@ def objects(self_, instance=True):
stacklevel=2,
)

cls = self_.cls
# We cache the parameters because this method is called often,
# and parameters are rarely added (and cannot be deleted)
pdict = cls._param__private.params
if not pdict:
paramdict = {}
for class_ in classlist(cls):
for name, val in class_.__dict__.items():
if isinstance(val, Parameter):
paramdict[name] = val

# We only want the cache to be visible to the cls on which
# params() is called, so we mangle the name ourselves at
# runtime (if we were to mangle it now, it would be
# _Parameterized.__params for all classes).
# cls._param__private.params[f'_{cls.__name__}__params'] = paramdict
cls._param__private.params = paramdict
pdict = paramdict

pdict = self_._cls_parameters
if instance and self_.self is not None:
if instance == 'existing':
if getattr(self_.self._param__private, 'initialized', False) and self_.self._param__private.params:
Expand Down

0 comments on commit 4d2f23c

Please sign in to comment.