-
Notifications
You must be signed in to change notification settings - Fork 213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cache the imported AsyncToSync and SyncToAsync classes on the Local class #288
base: main
Are you sure you want to change the base?
Conversation
…sses on the Local class. On first access, after they're imported the first time, hold a reference to them on the class itself. This nets a small increase in performance by avoiding going through the import machinery on every attribute access of a Local instance. Note that these are bound directly to the Local class rather than testing the instance (self) attributes, as doing so in the simple way would yield a RecursionError due to __getattr__ depending on _get_context_id
@smithdc1 get pinged. As you unintentionally reminded me about this, it might be of interest to you. It might not. It's on your radar now though either way :) |
Ooh, I get the point of this, but it feels a little weird to look at. That said, you're entirely right, imports are expensive to do at runtime. I think ideally I'd want a |
Well, yes and no. The worst kind of answer :) There was the similar initial idea of just pulling from sys.modules in the original ticket, and I've tried doing it on
Depending on what you mean, I can see that working. I could put the implementation roughly as-is behind a |
Honestly, having reconsidered it, maybe it is worth having a "lazy import" wrapper to keep this easier to read, and because asgiref is in the business of small optimisations at this point. I feel like the sys.modules option is very doable?
Then I believe we could just do |
Well, YMMV (between Python version differences and machine differences I can't guarantee this) but AFAI can tell, this is faster:
than doing this:
Mostly because it's avoiding that second invocation I suspect. Across the original ipython example upthread, it's consistently a difference; first the
and then the
I don't have a particular aversion to doing the cached import way if you prefer, I'm just here looking to reduce the distance between a For reference, because I've not previously provided it (tut!), the baseline for a threadlocal on my same machine is |
How about a Honestly, I'm not in this to make this hyper-competitive with even the normal concept of |
Seems to be a bit slower than doing 2 separate
Absolutely. The stark difference between the C threadlocal implementation and the Python one suggests that the only step which would make it competitive is shudder rewrite it in C. And I promise you I'm not asking anyone to do that :) The best that could feasibly be done is "competitive with the python threadlocal implementation" (having looked at the hoops jumped through, I'm not even going to suggest going toe-to-toe with it, it's far more arcane than
Sure, I understand that (and it's an admirable accomplishment to have got there. I can't state it enough) but I personally don't know what the timeframe of that migration might be for any single project (eg: Django's next minimum version is I don't think we're talking at cross purposes, but I think we've maybe got different ideas on goals, which wouldn't be surprising given only one of us has to maintain it -- cards on the table (and with utter respect, lest text not convey it) I don't see the point of the proposed As such, at this juncture, I'd recommend that we stop pitching ideas at each-other (and missing) and suggest just implementing the one you feel comfortable with supporting which gives the best improvement, which is If only Edit to add: those criticisms of |
Fair enough - let's just go with double cached_import for now then. |
FYI, the current version of import sys
from importlib import import_module
def cached_import(module_path, class_name):
# Check whether module is loaded and fully initialized.
if not (
(module := sys.modules.get(module_path)) and
(spec := getattr(module, '__spec__', None)) and
getattr(spec, '_initializing', False) is False
):
module = import_module(module_path)
return getattr(module, class_name) I guess as this package supports Python < 3.8 we'd need to convert to something like the following: import sys
from importlib import import_module
def cached_import(module_path, class_name):
# Check whether module is loaded and fully initialized.
ready = False
module = sys.modules.get(module_path)
if module:
spec = getattr(module, '__spec__', None)
if spec and getattr(spec, '_initializing', False) is False:
ready = True
if not ready:
module = import_module(module_path)
return getattr(module, class_name) |
See issue #269.
On first access, after they're imported the first time, hold a reference to them on the class itself. This nets a small increase in performance by avoiding going through the import machinery on every attribute access of a
Local
instance.Note that these are bound directly to the
Local
class rather than testing the instance (self) attributes, as doing so in the simple way would yield a RecursionError due to__getattr__
depending on_get_context_id
Using the same example as I provided in the issue:
vs prior to the patch:
This may still not be what Andrew was specifically hoping for, but given touching
self.x
in the simple/naive way is problematic (see above), I'm opting for this as the simplest possible solution from which discussion for improvement can follow.