Code

Ticket #16679: fast-signals2.diff

File fast-signals2.diff, 8.5 KB (added by akaariai, 3 years ago)

sender -> receivers cache

Line 
1diff --git a/django/db/models/base.py b/django/db/models/base.py
2index 71fd1f7..730e52f 100644
3--- a/django/db/models/base.py
4+++ b/django/db/models/base.py
5@@ -18,6 +18,7 @@ from django.db.models.query_utils import DeferredAttribute
6 from django.db.models.deletion import Collector
7 from django.db.models.options import Options
8 from django.db.models import signals
9+from django.db.models.signals import pre_init, post_init
10 from django.db.models.loading import register_models, get_model
11 from django.utils.translation import ugettext_lazy as _
12 from django.utils.functional import curry
13@@ -277,7 +278,7 @@ class Model(object):
14     _deferred = False
15 
16     def __init__(self, *args, **kwargs):
17-        signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
18+        pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
19 
20         # Set up the storage for instance state
21         self._state = ModelState()
22@@ -367,7 +368,7 @@ class Model(object):
23             if kwargs:
24                 raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0])
25         super(Model, self).__init__()
26-        signals.post_init.send(sender=self.__class__, instance=self)
27+        post_init.send(sender=self.__class__, instance=self)
28 
29     def __repr__(self):
30         try:
31diff --git a/django/db/models/signals.py b/django/db/models/signals.py
32index 48872e7..65e8f23 100644
33--- a/django/db/models/signals.py
34+++ b/django/db/models/signals.py
35@@ -2,15 +2,15 @@ from django.dispatch import Signal
36 
37 class_prepared = Signal(providing_args=["class"])
38 
39-pre_init = Signal(providing_args=["instance", "args", "kwargs"])
40-post_init = Signal(providing_args=["instance"])
41+pre_init = Signal(providing_args=["instance", "args", "kwargs"], use_caching=True)
42+post_init = Signal(providing_args=["instance"], use_caching=True)
43 
44-pre_save = Signal(providing_args=["instance", "raw", "using"])
45-post_save = Signal(providing_args=["instance", "raw", "created", "using"])
46+pre_save = Signal(providing_args=["instance", "raw", "using"], use_caching=True)
47+post_save = Signal(providing_args=["instance", "raw", "created", "using"], use_caching=True)
48 
49-pre_delete = Signal(providing_args=["instance", "using"])
50-post_delete = Signal(providing_args=["instance", "using"])
51+pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
52+post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
53 
54-post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
55+post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"], use_caching=True)
56 
57-m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"])
58+m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
59diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
60index ed9da57..bd9e461 100644
61--- a/django/dispatch/dispatcher.py
62+++ b/django/dispatch/dispatcher.py
63@@ -9,6 +9,7 @@ def _make_id(target):
64     if hasattr(target, 'im_func'):
65         return (id(target.im_self), id(target.im_func))
66     return id(target)
67+NONE_ID = _make_id(None)
68 
69 class Signal(object):
70     """
71@@ -20,7 +21,7 @@ class Signal(object):
72             { receriverkey (id) : weakref(receiver) }
73     """
74     
75-    def __init__(self, providing_args=None):
76+    def __init__(self, providing_args=None, use_caching=False):
77         """
78         Create a new signal.
79         
80@@ -31,6 +32,10 @@ class Signal(object):
81         if providing_args is None:
82             providing_args = []
83         self.providing_args = set(providing_args)
84+        self.use_caching = use_caching
85+        # For convenience we create empty caches even if they are not used
86+        self.sender_no_receivers_cache = set()
87+        self.sender_receivers_cache = {}
88         self.lock = threading.Lock()
89 
90     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
91@@ -107,6 +112,9 @@ class Signal(object):
92             else:
93                 self.receivers.append((lookup_key, receiver))
94         finally:
95+            if self.use_caching:
96+                self.sender_no_receivers_cache = set()
97+                self.sender_receivers_cache = {}
98             self.lock.release()
99 
100     def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
101@@ -144,6 +152,9 @@ class Signal(object):
102                     del self.receivers[index]
103                     break
104         finally:
105+            if self.use_caching:
106+               self.sender_no_receivers_cache = set()
107+               self.sender_receivers_cache = {}
108             self.lock.release()
109 
110     def send(self, sender, **named):
111@@ -165,10 +176,10 @@ class Signal(object):
112         Returns a list of tuple pairs [(receiver, response), ... ].
113         """
114         responses = []
115-        if not self.receivers:
116+        if not self.receivers or (self.use_caching and sender in self.sender_no_receivers_cache):
117             return responses
118 
119-        for receiver in self._live_receivers(_make_id(sender)):
120+        for receiver in self._live_receivers(sender):
121             response = receiver(signal=self, sender=sender, **named)
122             responses.append((receiver, response))
123         return responses
124@@ -197,12 +208,12 @@ class Signal(object):
125         receiver.
126         """
127         responses = []
128-        if not self.receivers:
129+        if not self.receivers or (self.use_caching and sender in self.sender_no_receivers_cache):
130             return responses
131 
132         # Call each receiver with whatever arguments it can accept.
133         # Return a list of tuple pairs [(receiver, response), ... ].
134-        for receiver in self._live_receivers(_make_id(sender)):
135+        for receiver in self._live_receivers(sender):
136             try:
137                 response = receiver(signal=self, sender=sender, **named)
138             except Exception, err:
139@@ -211,32 +222,46 @@ class Signal(object):
140                 responses.append((receiver, response))
141         return responses
142 
143-    def _live_receivers(self, senderkey):
144+    def _live_receivers(self, sender):
145         """
146         Filter sequence of receivers to get resolved, live receivers.
147 
148         This checks for weak references and resolves them, then returning only
149         live receivers.
150         """
151-        none_senderkey = _make_id(None)
152-        receivers = []
153-
154-        for (receiverkey, r_senderkey), receiver in self.receivers:
155-            if r_senderkey == none_senderkey or r_senderkey == senderkey:
156-                if isinstance(receiver, WEAKREF_TYPES):
157-                    # Dereference the weak reference.
158-                    receiver = receiver()
159-                    if receiver is not None:
160-                        receivers.append(receiver)
161-                else:
162+        receivers = None
163+        if self.use_caching:
164+            receivers = self.sender_receivers_cache.get(sender)
165+        if receivers is None:
166+            none_senderkey = NONE_ID
167+            senderkey = _make_id(sender)
168+            receivers = []
169+            if self.use_caching:
170+                self.lock.acquire()
171+            for (receiverkey, r_senderkey), receiver in self.receivers:
172+                if r_senderkey == none_senderkey or r_senderkey == senderkey:
173                     receivers.append(receiver)
174-        return receivers
175+            if self.use_caching:
176+                if not receivers:
177+                    self.sender_no_receivers_cache.add(sender)
178+                # Note, we must cache the weakref versions.
179+                self.sender_receivers_cache[sender] = receivers
180+                self.lock.release()
181+        non_weak_receivers = []
182+        for receiver in receivers:
183+            if isinstance(receiver, WEAKREF_TYPES):
184+                # Dereference the weak reference.
185+                receiver = receiver()
186+                if receiver is not None:
187+                    non_weak_receivers.append(receiver)
188+            else:
189+                non_weak_receivers.append(receiver)
190+        return non_weak_receivers
191 
192     def _remove_receiver(self, receiver):
193         """
194         Remove dead receivers from connections.
195         """
196-
197         self.lock.acquire()
198         try:
199             to_remove = []
200@@ -251,6 +276,9 @@ class Signal(object):
201                     if r_key == key:
202                         del self.receivers[last_idx-idx]
203         finally:
204+            if self.use_caching:
205+                self.sender_no_receivers_cache = set()
206+                self.sender_receivers_cache = {}
207             self.lock.release()
208 
209