Opened 7 years ago

Closed 7 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 Adam Johnson, 7 years ago

Has patch: set
Owner: changed from nobody to Adam Johnson
Status: newassigned

comment:2 by Adam Johnson, 7 years ago

Cc: me@… added

comment:3 by Markus Holtermann, 7 years ago

Triage Stage: UnreviewedReady for checkin
Version: 1.10master

LGTM

comment:4 by Adam Johnson, 7 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

comment:5 by Tim Graham <timograham@…>, 7 years ago

Resolution: fixed
Status: assignedclosed

In 22a60f8d:

Fixed #27513 -- Made Signal.send()/send_robust() a tiny bit faster.

Note: See TracTickets for help on using tickets.
Back to Top