Opened 9 years ago
Closed 9 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 , 9 years ago
| Has patch: | set |
|---|---|
| Owner: | changed from to |
| Status: | new → assigned |
comment:2 by , 9 years ago
| Cc: | added |
|---|
comment:4 by , 9 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