Skip to content

Commit 5eeb878

Browse files
authored
feat: more performance optimizations (#92)
The two big changes here are bypassing __init__ in a special case and directly setting instance attributes via reaching into self.__dict__. These are optimizations in a potential hot path that were isolated via exhaustive profiling and experimentation and save ~%16 in certain benchmarks. Do not try this at home.
1 parent 0389fa7 commit 5eeb878

File tree

2 files changed

+27
-17
lines changed

2 files changed

+27
-17
lines changed

packages/proto-plus/proto/message.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ def __new__(mcls, name, bases, attrs):
177177
#
178178
# m = MyMessage()
179179
# MyMessage.field in m
180-
mcls = type("AttrsMeta", (mcls,), opt_attrs)
180+
if opt_attrs:
181+
mcls = type("AttrsMeta", (mcls,), opt_attrs)
181182

182183
# Determine the filename.
183184
# We determine an appropriate proto filename based on the
@@ -295,7 +296,10 @@ def wrap(cls, pb):
295296
pb: A protocol buffer object, such as would be returned by
296297
:meth:`pb`.
297298
"""
298-
return cls(pb, __wrap_original=True)
299+
# Optimized fast path.
300+
instance = cls.__new__(cls)
301+
instance.__dict__["_pb"] = pb
302+
return instance
299303

300304
def serialize(cls, instance) -> bytes:
301305
"""Return the serialized proto.
@@ -319,11 +323,7 @@ def deserialize(cls, payload: bytes) -> "Message":
319323
~.Message: An instance of the message class against which this
320324
method was called.
321325
"""
322-
# Usually we don't wrap the original proto and are force to make a copy
323-
# to prevent modifying user data.
324-
# In this case it's perfectly reasonable to wrap the proto becasue it's
325-
# never user visible, and it gives a huge performance boost.
326-
return cls(cls.pb().FromString(payload), __wrap_original=True)
326+
return cls.wrap(cls.pb().FromString(payload))
327327

328328
def to_json(cls, instance) -> str:
329329
"""Given a message instance, serialize it to json
@@ -373,7 +373,7 @@ def __init__(self, mapping=None, **kwargs):
373373
if mapping is None:
374374
if not kwargs:
375375
# Special fast path for empty construction.
376-
self._pb = self._meta.pb()
376+
self.__dict__["_pb"] = self._meta.pb()
377377
return
378378

379379
mapping = kwargs
@@ -383,15 +383,13 @@ def __init__(self, mapping=None, **kwargs):
383383
# that it will not have side effects on the arguments being
384384
# passed in.
385385
#
386-
# The `__wrap_original` argument is private API to override
387-
# this behavior, because `MessageRule` actually does want to
388-
# wrap the original argument it was given. The `wrap` method
389-
# on the metaclass is the public API for this behavior.
390-
if not kwargs.pop("__wrap_original", False):
391-
mapping = copy.copy(mapping)
392-
self._pb = mapping
386+
# The `wrap` method on the metaclass is the public API for taking
387+
# ownership of the passed in protobuf objet.
388+
mapping = copy.copy(mapping)
393389
if kwargs:
394-
self._pb.MergeFrom(self._meta.pb(**kwargs))
390+
mapping.MergeFrom(self._meta.pb(**kwargs))
391+
392+
self.__dict__["_pb"] = mapping
395393
return
396394
elif isinstance(mapping, type(self)):
397395
# Just use the above logic on mapping's underlying pb.
@@ -420,7 +418,7 @@ def __init__(self, mapping=None, **kwargs):
420418
params[key] = pb_value
421419

422420
# Create the internal protocol buffer.
423-
self._pb = self._meta.pb(**params)
421+
self.__dict__["_pb"] = self._meta.pb(**params)
424422

425423
def __bool__(self):
426424
"""Return True if any field is truthy, False otherwise."""

packages/proto-plus/tests/test_message.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,15 @@ class Squid(proto.Message):
216216
s = Squid()
217217
with pytest.raises(AttributeError):
218218
getattr(s, "shell")
219+
220+
221+
def test_setattr():
222+
class Squid(proto.Message):
223+
mass_kg = proto.Field(proto.INT32, number=1)
224+
225+
s1 = Squid()
226+
s2 = Squid(mass_kg=20)
227+
228+
s1._pb = s2._pb
229+
230+
assert s1.mass_kg == 20

0 commit comments

Comments
 (0)