Monday, November 5, 2012

Optimizing event handling

Recently, while doing a round of profiling, we found out that event processing on the Form Runner summary page when paging and searching was taking a long time to execute (in the order of hundreds of milliseconds). So we set to improve this.

In XForms as in plain HTML, event dispatch follows the processing model defined by the DOM Events specification. An event conceptually travels from the root of the document to the target, and then back to the root of the document, in a capture phase, a target phase, and a bubbling phase. It also supports features such as event cancellation, stopping event propagation, and preventing the execution of the default action. And of course, an important part is to figure out the events handlers to run during each phase. So it's potentially a lot of work, as shown on this diagram from the DOM Events spec:

A great benefit of XForms is that the installation of event handlers is primarily done declaratively, thanks to XML Events. (XForms doesn't say that you cannot support other ways, with script for example. But in Orbeon Forms we exclusively support the declarative way.) The declarative aspect means that all event handlers are known in advance and can be analyzed statically, and so we just implemented such a static analysis!

Here is how it works. For an event with a given event type (or name) dispatched to a control, we determine whether in general there can be event handlers to run, and if so during which phase and for which event observers. In other words, we gather the maximum amount of information possible so that when an event of this type is concretely dispatched, we quickly know what to do.

For example, let's say an xforms-enabled event is dispatched to a input control. If we find that there are no event handlers for that event, we remember that fact so that the next time xforms-enabled is dispatched to that same input control, we know we can just skip all the processing associated with event handlers.

On the other hand, let's say xforms-value-changed is dispatched to that same control, and there is a single event handler for it: we store that information alongside the phase and the id of the observer to which the handler is attached. This way, the next time that event is dispatched, we can very quickly just run that single event handler associated with that observer.

Things are not quite that simple in practice (we must handle XForms repeats, for example), so there is some work to do each time an event is dispatched, but it's much smaller than it used to be.

Event handler information is gathered lazily into a cache as events are dispatched. This has the benefit of doing the work only for events that are actually dispatched. (Also, in XForms, the name and target of events can be dynamic, which means that it isn't possible to analyze everything in advance anyway in all cases. So laziness is a good idea.) The cache is shared between form instances that use the same form definition. So if 100 users visit a given form, they all benefit from the cache.

The result is not only a faster Form Runner summary page, but also faster event processing in the XForms engine in general.

No comments:

Post a Comment