[Development] QML: grouping property changes for a single callback invocation

christopher.adams at Nokia.com christopher.adams at Nokia.com
Tue Mar 6 02:12:19 CET 2012


Hi Alberto,

Firstly, this is a really interesting topic, so thanks for bringing it up, and even more thanks for offering to help investigate and implement changes!
Secondly, this problem is one that we have considered before, and the ideal solution is probably some "binding is dirty" flag, combined with "pull properties when required" mechanism (rather than the current "always re-evaluate bindings on change propagation" method).  But that solution is a lot of work, so your suggestion may indeed be a very nice half-way-there compromise.  But it isn't without its risks, so more comments in-line.

> > It's pretty much as you described. With the slight addendum that what
> > you're asking for is probably not possible (in the general case). X
> > and Y are independent variables, one may change without the other. So
> > the code can't just wait for a signal which may or may not ever come.
> > If you have a solution for this problem, I'm all ears :) .
> 
> I've been giving it some thoughts, but my idea is very rough at the moment.
> It's about queuing all the invocations of property signals (and property
> bindings) and fire them in an idle callback to be invoked later.
> So, supposing that you have this QML code:
> 
> =======
> property int width: x + sizeX
> 
> onXChanged: functionA()
> =======
> 
> instead of connecting the onXChanged() signal to function A and to the re-
> evaluation of the "width" property, you would connect to a slot that simply
> does something like this:
> 
> {
>     listOfChangedProperties.append("x");
>     /* next line sets up an idle event (QTimer with time set to 0,
>      * maybe), if one is not set already */
>     setupIdleEvent();
> }
> 
> Then, when the idle callback will be invoked, it will go through
> listOfChangedProperties and for each of them invoke the slots connected to
> them, and recompute the values of other properties depending on them,
> each time marking the dependent slot/property as processed, in order to
> avoid processing it more than once during the same idle invocation.

One important note is that this sort of signal coalescing does have performance implications.  Also, we have no guarantees of when the idle callback will be invoked, which could be problematic (more on this later). 

Also note that the LHS property will change as a result of the slot invocation in the idle callback, so in order for this suggestion to work properly, you'd need to enable cascade-updates of bindings (ie, current behavior) during that callback (since doing the coalescing and setupIdleEvent() for that change would almost guarantee that the change wouldn't be calculated until the next 16 ms timeslice - more on this later).

> 
> This would also mean that if both "x" and "sizeX" change before the idle
> callback is invoked, the "width" property expression would have to be
> computed only once.

The savings from this would be substantial only if the average application includes a significant number of complex bindings, and if multiple properties within the binding change simultaneously.  If the cost of doing the coalescing in every case outweighs the benefit of avoiding recalculating both in a minority case, then we should not do this.  However, I suspect that complex bindings are quite common, and I'm certain that the cost of doing even one redundant binding evaluation would outweigh the cost of coalescing, so I'm fairly certain that this would turn out to be a net-win.  But it needs to be benchmarked thoroughly. 

> 
> I didn't have a look at QDeclarative source code yet, so I'm all ears for your
> opinion (and hints, in case I want to spend some time to investigate this
> possibility).

The major problem I have with this suggestion is the use of the idle-timer.  The problem is this: for a 60fps "Velvet" application, you have 16 ms to do the processing of all animations, binding updates, signal handlers, view delegate creation and so forth, then synchronise with the render thread.  By depending on the idle callback, and doing all bindings updates in there, you run the risk of updates not being propagated during the critical time slice, resulting in unsmooth animations, etc.

One possibility would be to use the unified timer (triggered by vsync) to trigger coalescing and then evaluation, perhaps with some form of co-operative yield between individual binding evaluations to avoid skipping frames (see, for example, the threaded instantiation logic of the asynchronous loader).

I would be very interested in seeing a prototype of this solution (even with just using an idle callback, as a first iteration) that we could benchmark and discuss more thoroughly.  I'd also be interested to hear what Aaron Kennedy, Kent Hansen and Michael Brasser think about the idea, as they would have a better grasp of the possible problems that could arise from this sort of change, than I do.

Cheers,
Chris.




More information about the Development mailing list