-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
In #10314 we introduced a new SingletonGate
class which is used as the parent for many standard library gates. This class enables us to use a single shared instance for these gates which greatly reduces our memory overhead for repeated gates in a circuit. However, the manner in which implemented this object reuse is by overloading __new__
in the SingletonGate
class which determines if the instance should be a singleton or a mutable copy and returns that. However, using __new__
has noticeable impact on runtime for creating new objects. @jakelishman outlined some of the reasons for this overhead in his comment on #10865:
Fwiw, there's a bunch of "hidden" overhead of
SingletonGate.__new__
in that:
- making the
__new__
an extra Python-space call overobject.__new__
adds cost of itself- even if we return the singleton instance from
__new__
, the resulting__init__
still gets called if the type of return value is in the inheritance chain of the original object.
One potential way around this is to have the singleton class be a metaclass that overrides
type.__call__
with a new method that returns the singleton instance without calling the__new__
machinery at all. That will avoid__init__
from being called at all, so > we'll gain really a bunch of the time there. The downside is that there's a metaclass conflict:Operation
's metaclass isABCMeta
, so we can't (naively) makeSingletonGate
> a metaclass. However, we could makeSingletonGateMeta
derive fromABCMeta
, which would resolve the conflict, even if we'd need to take care to maintain that relationship wereOperation
ever to change.
Originally posted by @jakelishman in #10865 (comment)
This overhead for object creation goes from previously being ~666ns for a class directly inheriting from Gate
to ~5us changing the parent to SingletonGate
. We should investigate different techniques to mitigate the runtime impact here.