Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor optimizations in hot codepaths accessing class parameters #893

Merged
merged 5 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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