Hi, On Fri, Oct 22, 2010 at 10:24 AM, Owen Taylor <otay...@redhat.com> wrote: > Is painting well behaved? Inherently - no. We can easily get in > situations where we can spend all our time painting and no time doing > anything else.
That's the point of the up-to-5ms-of-dispatch thing (or the wait-for-frame-complete thing), though, right? We can force painting to be well behaved by having a period during each frame where painting is not ready for dispatch or has a lower priority. During that period anything else can run - even idles can run. (Or if we like, even default idles can run but not low idles, or only default priority not idles, could be implemented. Just has to be defined.) I don't see the need to choose between painting-starves-everything and everything-starves-painting. The "dispatch everything but painting" gap can have a max length, not a fixed length - if you do get everything else done you can proceed to paint. (Well, that's what we do with the 5ms. I guess it might be harder to do with a pure GMainLoop solution but you could do it, by having the paint source dynamically change its priority or by having a super-low-priority paint source that's always ready plus a high-priority one that is only ready when frame-complete or after an interval.) The effect of max rather than fixed length "everything else" gap is that if painting truly needs the whole frame interval (rather than interval minus gap), painting may still keep up on those frames where there's little else to do. The idea is there's a period in which painting will be well-behaved by yielding - it isn't allowed to defer everything else indefinitely / 100% of the time. This changes your max time you theoretically have to paint from 1/fps seconds to (1/fps - gap) seconds. However even if you can't paint in (1/fps-gap) you will only lose a frame due to the gap when there's really stuff to do inside the gap. And if you can't afford a reasonable gap you probably really are hosed anyhow. There is already a fixed max on paint time in order to look good, so that doesn't change, the max is just tweaked (and can flex up to the absolute max, 1/fps, whenever possible). The idea is that if your average paint-cpu and everything-else-cpu adds up to less than 100% cpu, we shouldn't need to drop frames. However with a starvation model, you drop frames even if you can keep up on average, due to lumpiness, right? so that's the idea of only yielding for a gap - if you get a lump in your everything-else, such as a flood of messages, try to spread it over a few frames. In practice I think that's what we're talking about. Say I have a spinner animating smoothly, and I get some flood of IO or dbus or whatever it is. Do I handle that flood in 30ms and my spinner hiccups, or do I spread the flood in chunks over several frames, and my spinner stays pretty. As an app developer I think what I want is the latter. But I also don't want to wait for the spinner to go away before handling this flood. I want to smooth the flood over multiple frames, but not defer it indefinitely, I want to make progress on every frame. I don't think that can be expressed by just having a fixed priority on paint and a fixed priority on my handler; there has to be some way in which the priorities "flip" during the frame. > Are IO completions and IPC well behaved? Well that's really up to the > application.... however, they have to be *somewhat* well behaved in any > case. What's hard I think is to make them well behaved in the aggregate and on every single frame. i.e. it's hard to avoid just randomly having "too much" to dispatch from time to time, then you drop 3 frames, it just looks bad. But as long as you're OK *on average* this can be solved by spreading the dispatch of everything else across more than one frame, instead of insisting on doing it all at once. If you aren't OK on average, that's a problem the app is just going to have to solve. > If I have a GIO async callback that fills a treeview, there is one > pathology where my callback gets called so frequently that we never get > get to repaint. But what may happen instead is that I get so much data > in a *single* callback that I block the main loop for an unacceptably > long period of time. So we always will have the requirement that > callbacks from the main loop must be *individually* short. Yes. I think things can be made to work pretty well with just this requirement. > Making IO > completions and IPC highest priority makes this requirement a bit more > stringent - it means that callbacks from the main loop must be *in > aggregate* short. That callbacks from the mainloop aren't allowed to do > expensive stuff, but instead must queue it up for an idle at lower > priority. A priority lower than paint is the right priority for most stuff, IMO, as long as we're going to dispatch that lower priority at least for some gap per frame. If the presence of an animation means that we aren't going to dispatch lower-than-paint sources until we aren't animating, then at that point I think most stuff would have to be higher than paint, which breaks paint. Doing only painting or only other stuff in any frame just isn't right. What's right is to do painting plus "up to a fixed amount of everything else which on average is all of everything else, but can be less than all sometimes if there's too much" on each frame. > While a two-part system like this sounds like a huge pain for > application writers - it does have the big advantage that everybody gets > a say. If we just cut things off after a fixed time and started > painting, then we could end up in a situation where we were just filling > the treeview and painting, and never processing D-Bus events at all. If painting is a higher than default priority you could still add sources at an even higher priority, or you could hook into the paint clock in the same place events, resize, etc. hook in to force some queue to be drained before allowing paint to proceed. Also if you have a handler that just does your whole queue of whatever at once, it effectively does run on every frame and compress it all, even if it's an idle - since the main loop can't interrupt a dispatch in progress, and the gap means that we'll probably run the dispatch handler once on all nonpaint sources in a typical frame. The litl "manual main loop" (not using GMainLoop, manually iterating) always forces at least one nonblocking iteration in between draining the event queue and painting, even if 5ms was spent on the event queue, as paranoia to be sure some progress is always made. So a default-priority handler that drains the whole queue always compresses. > Well, sort of - the main loop algorithms are designed to protect against > this. *All* sources at the current priority are collected and dispatched > once before we check again for higher priority sources. But since > sources have different granularities and policies, the effect would be > some unpredictable. The behavior of the GDK event source is to unqueue > and dispatch one X event source per pass of the main loop, D-Bus and GIO > probably do different things. dbus works like the GDK source (because it copied it). One message per dispatch at default priority. I'm not sure how gdbus works. I think what dbus does works well, as long as painting is 1) above default priority and 2) not ready for dispatch for at least some time during each frame length. The thing is that as long as "everything but painting" is basically sane, then the "up to 5ms" or "while waiting for vsync" gap is going to be enough to dispatch everything. If you get a flood of dbus messages or whatever though, then you start spreading those over frames (but still making progress) instead of losing frames indefinitely until you make it through the queue. It's just less bad, to spread dispatching stuff out over a few frames if you get a flood, than it is to drop a few frames. > Right now event compression in Clutter counts on events getting unqueued > from the X socket at the default priority and then stored in an internal > queue for compression and dispatching before painting. Going to a system > where painting was higher priority than normal stuff would actually > require 3 priorities: Event queueing, then painting, then everything > else. [*] Events have to be worked into this so they get done before painting, but that seems straightforward. It can be done with an event source even higher priority than painting, or by having a "do all events now" hook that is part of painting, I think. I guess in litl shell we even have a time limit on the event compression; we will stop compressing events if we exceed the 5ms. Separate discussion if that's desirable. We count event processing against the "yield gap" time limit though we do always iterate non-event stuff at least once. > But can we say for sure that nothing coming in over D-Bus should be > treated like an event? Generally, anything where the bulk of the work is > compressible is better to handle before painting. No, but I'd suggest this is rare, and easier for apps to special-case, compared to it being essentially wrong to ever do anything at default priority. > An example: If we have a change notification coming over D-Bus which is > compressible - it's cheap other than a triggered repaint. Say updating > the text of label. And combine that with our GtkTreeView filler, then we > might have: > > Fill chunk of tree view > Change notification I'd tend to handle this by having a way in gdbus to say "yank me all messages matching xyz out of the queue now" and then you would add either a higher-priority-than-paint handler or a hook into the clock before paint, and in that handler/hook you'd yank the change notifications from the queue. Or you could do it as, if you handle one change notification, at that point you go ahead and yank any other pending ones out of the queue. > Yep. But this cuts both ways - if we handle painting first and > everything else uniformly second, then we can't *up* the priority of > anything coming over D-Bus. Either higher-priority source or a hook in the clock or queue lookahead, right? > I'm not sure how well the model you are proposing here: > > - A single GSource dispatch cycle is a single queue element > - Each queue element is individually short > - All queue elements in aggregate may be long > > Really corresponds to the reality of GSource usage. It's the way dbus-glib worked anyhow. I'm not sure how gio works. (Though I think it does install default-priority handlers for the GAsyncResult returns, I'm not sure if the usual way to read a stream would be to get a series of chunks back into the main thread.) > - We can do a reasonable job scheduling painting versus everything > else. (I think about the best you can do is to try and spend 50% > of the time painting and 50% of the time doing everything else. > Anything that's more sophisticated than that is tweaking around > the edges.) Basing this on waiting for frame-complete seems nicer than waiting some fixed interval though right? The fixed interval is hopefully not needed. If stuck with a fixed length, I'd say more like 25/75 (which is roughly the 5ms) - just based on painting usually needs a lot more time than "other stuff" does, in my experience, and because it's more OK to slow down other stuff than it is to slow down painting. But remember, if there's nothing to dispatch, just go ahead and start painting - it isn't required to wait the "yield to other stuff" interval if there's nothing to yield to. The "yield to nonpainting stuff" gap is a max length not a fixed time. >> Why not make paint priority greater than the default priority, and so >> most things should be default, and idle is reserved for things that >> it's acceptable to starve? > > I guess the basic question here is whether "most stuff" is updating the > text of a label or whether most stuff is adding 10000 elements to a > GtkTreeView. If most things that applications are doing in response to > timeouts/IO completions/IPC are cheap and take almost no time, then we > gain compression and efficiency by doing them before painting and we can > ask the applications to code specially for the rest. If most things are cheap and take no time, then they will run in the yield gap and then painting can start right away as soon as there's nothing left to dispatch. That's your average case and it works. But we can also make the lumpy flood case work - just stop iterating if it's time to paint now, finish doing the leftover work on the next frame. Spread out the lump. Why not? Almost everything Just Works in this scenario. The only thing you have to special case is if compression is important to you, and "compress during the yield gap" is not good enough (i.e. if you want to compress absolutely everything, not just stuff that happens within a certain time), then you need to run the queue in your dispatch, rather than dispatching one item per iteration. > Note: I've talked about "idles" above for deferring to the lower > priority, but something more sophisticated might be useful - anything > deferred should be making *some* progress - it shouldn't be completely > starved. It might also be useful to be able to do > 'while (!my_deferred_work_function_should_yield()) > { add row to tree view }.' Exactly, yep. I just don't think paint-starves or other-stuff-starves are what's wanted. Havoc _______________________________________________ gtk-devel-list mailing list gtk-devel-list@gnome.org http://mail.gnome.org/mailman/listinfo/gtk-devel-list