Opened 8 years ago
Closed 8 years ago
#27513 closed Cleanup/optimization (fixed)
Optimize Signal.send a tiny bit
Reported by: | Adam Johnson | Owned by: | Adam Johnson |
---|---|---|---|
Component: | Utilities | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | me@… | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Signals often have no listeners so their send()
is a no-op. #16639 tried to optimize the model init signals to be faster for this case, but came out too complicated.
One micro optimization can be done in the current no-op code is to avoid assigning the empty list to a variable before returning it, which is less operations in the Python virtual machine, for example:
In [5]: import dis In [6]: def foo1(): ...: responses = [] ...: if True: ...: return responses ...: In [7]: dis.dis(foo1) 2 0 BUILD_LIST 0 3 STORE_FAST 0 (responses) 3 6 LOAD_GLOBAL 0 (True) 9 POP_JUMP_IF_FALSE 16 4 12 LOAD_FAST 0 (responses) 15 RETURN_VALUE >> 16 LOAD_CONST 0 (None) 19 RETURN_VALUE In [8]: def foo2(): ...: if True: ...: return [] ...: In [9]: dis.dis(foo2) 2 0 LOAD_GLOBAL 0 (True) 3 POP_JUMP_IF_FALSE 10 3 6 BUILD_LIST 0 9 RETURN_VALUE >> 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
Timing the two above example functions gives:
In [13]: %timeit foo1() 10000000 loops, best of 3: 160 ns per loop In [14]: %timeit foo2() 10000000 loops, best of 3: 121 ns per loop
Change History (5)
comment:1 by , 8 years ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
comment:2 by , 8 years ago
Cc: | added |
---|
comment:4 by , 8 years ago
send()
with receivers can be optimized too with a list comprehension, avoiding temp var response
and method calls on responses
:
In [1]: %cpaste Pasting code; enter '--' alone on the line to stop or use Ctrl-D. :def before(self, sender, **named): for receiver in self._live_receivers(sender): response = receiver(signal=self, sender=sender, **named) responses.append((receiver, response)) return responses def after(self, sender, **named): return [ (receiver, receiver(signal=self, sender=sender, **named)) for receiver in self._live_receivers(sender) ] :<EOF> In [3]: dis.dis(before) 2 0 SETUP_LOOP 66 (to 69) 3 LOAD_FAST 0 (self) 6 LOAD_ATTR 0 (_live_receivers) 9 LOAD_FAST 1 (sender) 12 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 15 GET_ITER >> 16 FOR_ITER 49 (to 68) 19 STORE_FAST 3 (receiver) 3 22 LOAD_FAST 3 (receiver) 25 LOAD_CONST 1 ('signal') 28 LOAD_FAST 0 (self) 31 LOAD_CONST 2 ('sender') 34 LOAD_FAST 1 (sender) 37 LOAD_FAST 2 (named) 40 CALL_FUNCTION_KW 512 (0 positional, 2 keyword pair) 43 STORE_FAST 4 (response) 4 46 LOAD_GLOBAL 1 (responses) 49 LOAD_ATTR 2 (append) 52 LOAD_FAST 3 (receiver) 55 LOAD_FAST 4 (response) 58 BUILD_TUPLE 2 61 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 64 POP_TOP 65 JUMP_ABSOLUTE 16 >> 68 POP_BLOCK 5 >> 69 LOAD_GLOBAL 1 (responses) 72 RETURN_VALUE In [4]: dis.dis(after) 10 0 LOAD_CLOSURE 0 (named) 3 LOAD_CLOSURE 1 (self) 6 LOAD_CLOSURE 2 (sender) 9 BUILD_TUPLE 3 12 LOAD_CONST 1 (<code object <listcomp> at 0x1068b5930, file "<ipython-input-1-cece5b56e5de>", line 10>) 15 LOAD_CONST 2 ('after.<locals>.<listcomp>') 18 MAKE_CLOSURE 0 11 21 LOAD_DEREF 1 (self) 24 LOAD_ATTR 0 (_live_receivers) 27 LOAD_DEREF 2 (sender) 30 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 33 GET_ITER 34 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 37 RETURN_VALUE
https://github.com/django/django/pull/7581