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
ParkingLot and Queue are embarrassingly slow #272
Comments
|
An advantage of doing immediate hand-off in |
...though we could also fix #63 by keeping a count of how many tasks have been unparked from |
The Queue algorithm above requires that the put lot use strict FIFO, to avoid weirdness where we wake up the wrong put task. (Like, if you have a capacity 0 queue, it should never be the case that a task is still asleep after its particular value has been returned by Oh ugh, it also makes I guess we could use a queue that maps tasks to the queue values, though this is problematic for the tasks that have woken up (and particular if they want to queue another item!). So I guess it would have to be self._queued = deque()
self._put_waiting = OrderedDict() and Or... we can tweak it. Dunno, need to think about this when I'm mor eawake. |
Use OrderedDict instead of SortedDict, and don't make park() pay for repark(). See python-triogh-272 for context. This doesn't make ParkingLot as fast as a version that just holds a set of tasks and does def unpark_all(self): while self._waiters: reschedule(self._waiters.pop()) def unpark(self): if self._waiters: reschedule(self._waiters.pop()) but it's about half-way in between the old version and this minimal version on the broker microbenchmark.
This was written for other reasons, but it includes a sketch of how a more efficient |
See #321 for dropping |
based on ideas and code from python-trio#272
based on ideas and code from python-trio#272
I haven't looked at the use case here deeply, but often I find that heapq is best for jobs initially trying to use OrderedDict. |
@belm0 in this case we need a simple FIFO queue, except with the ability to quickly delete arbitrary items (e.g. when a Both |
Oh, right. Yes, this is resolved on my end. I’ve using the new queue implementation for a while. We might find room for improvement going forward but we’re far off “embarrassingly slow” territory :) |
@sorcio sent a small benchmark that I don't think I can share publically, but that implements a simple message broker: clients can connect and say "send this blob of data to channel X" or "please send me the data from channel X", and it brokers between them. His initial version used a
trio.Queue
for message passing and atrio.hazmat.ParkingLot
to implement a littleEvent
-like object. It turned out that some ridiculous proportion of runtime was going into these, like when I dropped in a some simple non-fair versions using aset
to track sleeping tasks, the overall execution got 2x faster.Looking at
ParkingLot
, I think at least for now we should be able to make it much faster without losing any features, by replacing theSortedDict
-of-tickets with anOrderedDict
-of-tasks. The trick to getting rid of the tickets is that when we requeue, we can go modifytask._abort_fn
directly. (Hey, we're in_core
, might as well make use of it!) This means that requeuing tasks from one lot to another would put them behind the tasks that were waiting there rather than preserving the global order, but I think that's fine, maybe even better than the current system. This also means if we wanted to add tags to sleeping tasks to implement task-fair RWLocks, we could do that for "free", by storing it in the value entry in theOrderedDict
(otherwise the value would just beNone
).I'm not sure if switching to a shared global ParkingLot API would be better or not. It would allow us to skip allocating a
ParkingLot
entirely for uncontended objects – like aQueue(1)
that just gets used once wouldn't need a put lot at all. OTOH with a global object we'd have to jump through several dict lookups each time to find it. It likely doesn't make a big difference either way, but it's probably worth benchmarking at some point.I'm not sure if the Queue issue is the same or different – Queue obviously depends on ParkingLot, but has a somewhat overcomplicated implementation on top of that using a bunch of semaphores and stuff.
We could redo Queue as:
put
: if there's aget
waiting, hand it the object; otherwise, immediately append to deque, and if over capacity, go to sleep.get
: if there's data in the queue, take it and wake aput
; otherwise, go to sleep and wait to be handed some data.Downside: this requires
ParkingLot
grow back the ability to hand off an object to the woken task. I think this is OK? Upside: in this model,Queue(0)
automatically works correctly. Neutral: this actually give even stricter FIFO fairness than we have currently -- right now if G1 and G2 block inget
, and then P1 and P2 callput
in quick succession so that G1 and G2 are both woken up on the same tick, then it's possible for G1 to get P2's object and G2 to get P1's. This is only possible when G1 and G2 wake up on the same tick though, so I don't know that it matters – what we have now is not strict FIFO, but that's not the same as fair and it'd be hard to say that what we have isn't fair. Actually, if we're OK with this kind of fairness, then maybe we can drop the requirement of handing off data throughParkingLot.unpark
?Maybe drop the whole
join
API too because ugh, it's this second specialized synchronization primitive bolted ontoQueue
that's rarely useful.The text was updated successfully, but these errors were encountered: