Re: Doubts about GPeriodic



I think we're largely agreeing on the big picture here - that priorities
don't work so there has to be arbitration between painting and certain
types of processing.

I think the points where aren't entirely aligned are: what is a suitable
method of arbitration, and whether the arbitration is something that
happens normally, or you have to opt into it as a "background task".

About the method of arbitration: if we look at the idea of reserving a
fixed 5ms or painting during the "waiting for completion" gap, that
works well if the computation and painting are essentially unrelated -
if we are painting a GtkExpander expanding smoothly while we are filling
a treeview. It doesn't work so well if the painting is being triggered
*by* the computation. If we are using 12ms of CPU to relayout and
repaint and only filling the treeview in the intermediate 4ms, then
we've increased the total time to complete by a factor of 4.

If course, having each chunk of a large computation trigger the same
amount of compressible paint work is pathological - ideally we'd be in a
situation like incrementally laying out a GtkTextView - only the first
chunk triggers a full repaint, subsequent chunks only cause the
scrollbar to redraw and the scrollbar redraw is cheap.

But pathological or not, I think it's also common. This is where my
suggestion of a 50% rule comes in. It's a compromise. If we're lucky
repainting is cheap, we're hitting the full frame rate, and we're also
using 75% of the cpu to make progress. But when drawing takes more time,
when there is real competition going on, then we don't do worse than
halve the frame rate.

(This continues to hold in the extreme - if redrawing is *really* slow -
if redrawing takes 1s, then certainly we don't want to redraw for 1s, do
5ms of work, redraw for another 1s, and so forth. Better to slow down
from 1 fps to 0.5fps than to turn a 1s computation into a 3 minute
computation.)

[...]

> > 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.

I think this is a very good point, especially when we are trying to keep
a video from stuttering or similar cases where the redrawing is
unrelated to the painting. Unfortunately, however, we can't spread
things out if the work occurs at the layout stage - not an uncommon
circumstance.

(There may be a slight overestimation going on about how bad it is to
drop frames- early versions of Clutter and of the Clutter/Tweener
integration just didn't handle the computations correctly, so dropped
frames were causing velocity stutters.)

[...]

> > 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.

Yes, the X event queue could be force-drained before painting (with
considerable adaption to the current interfaces for doing things like
GTK+/Clutter integration.)

> 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.

[...]

> 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.

Certainly in the case where everything is fast, it doesn't matter much -
all the approaches collapse to the same end effect.

My concern is that long-running background tasks, when they exist,
shouldn't slow down the processing of everything else. 

Yes, we can avoid that by making sources that run the entire queue in a
single dispatch, but I don't see you can make that decision about queue
design for something generic like D-Bus or GIO.

[...]

> > 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.

The things that are slow in the main thread for most apps that I've ever
profiled are layout and painting. Everything else going is just setting
that up.

If that's not the case, then the process is likely very specialized for
background processing - GIMP applying filters, Evolution talking to IMAP
or whatever.

> > 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.

Lookahead, out-of-order compression ala XCheckWindowEvent() is something
that has always seemed extremely ugly to me. It's extra code to create
compression inserted in specific cases to fix problems that you've found
after the fact, rather than compression flowing naturally out of the
code. Every single GTK+ program benefits from a ton of compression
because of gtk_widget_queue_resize(), but it's invisible to the
programmer. People just think gtk_label_set_text() is nearly free.

(There actually was time when gtk_label_set_text() was slow - when you
had to worry about it in your app.)

> > 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 suppose you could tag D-Bus methods in some fashion, and have the
D-Bus client library reorder the messages. But again, that depends on
the user recognizing where compression is useful.

> > 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.

It's how dbus-glib worked if all method completed quickly.

> (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.)

Looking at interface of g_input_stream_read_async() it does seem like
like it will give you a fixed size chunk of data, rather than "the data
that is currently available".

[....]

> >> 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?

Because long-running background tasks break compression.... that's my
concern - that spreading out the lump can increase work. (I'm uncertain
whether this is something that is mostly theoretical, or something that
will have people scratching their heads. It's certainly not the *normal*
case since it involves multiple things going on at once.)

> 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.

"The queue" is largely not in your control - it's whatever GIO or gdbus
does. If they run the full queue or schedule a separate source for each
callback, then painting won't happen until that's done. If they only do
a fixed amount of work before yielding then compression breaks.

- Owen




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]