Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Request for Performance Pattern Ideas

56 views
Skip to first unread message

Kyle Machulis

unread,
Feb 8, 2013, 2:27:34 PM2/8/13
to dev-...@lists.mozilla.org
In an effort to standardize some of our performance gains across
FirefoxOS apps, we're compiling a list of patterns for performance
wins in both gecko and gaia. Since we've had lots of people looking at
performance at the moment, now seems like a good time to do this since
it should be fresh on everyone's mind.

If you have any general ideas about how to improve performance in apps
or platform, or stories of things that have worked for you in the
past, please post them here. We're looking for general procedures that
we can actually extract numbers from at a later point.

An example:

With contacts, we've found that the use of IndexedDB getAll() was
quite slow due to the number of records fetched was usually quite
large. This problem is now being fixed with cursors. However, for
mostly static sets of small records, getAll() is a fine solution, and
adding more cursors when they are not needed could slow things down.
What we'd like to have at the end of this is some solid numbers that
say what the upper bounds of record numbers should be for using getAll
versus switching to cursors. But for now, we just want the idea of
"getAll(), then cursors", then we'll move onto figure out the
specifics.

As we get some patterns hashed out and distilled on the mailing lists,
I'll be moving them over to be subtasks of the metabug 839300
(https://bugzilla.mozilla.org/show_bug.cgi?id=839300). Each subtask
will start with the pattern, then we'll work out ways to verify
specific numbers for the pattern.

David Flanagan

unread,
Feb 8, 2013, 6:55:10 PM2/8/13
to dev-...@lists.mozilla.org
Thanks Kyle! This is an excellent idea.

Here are my performance anecdotes:

For gallery performance, I think I'm going to be abandoning indexedb
entirely. The way my app is structured, it keeps an in-memory copy of
all of its data anyway, and I never need to do complicated queries on
it, so I've realized that I can store that data in a flat file and read
the file at startup. It looks like I can read that file in about the
same time it takes to just open the database, so this is a big win in my
case.

With both IndexedDB and DeviceStorage, I've attempted to start the db
opening process or the file reading process in a non-deferred script
that is loaded first. This is in the hopes that by the time the document
all the deferredscripts are loaded and I get a DOMContentLoaded, I'll
have an open db waiting for queries, or I will have received my file
from device storage. In practice, though, it appears that loading the
document and its scripts take priority and that attempting to start my
I/O earlier doesn't make things substantially faster.

Please share your tips. What have you done to make your apps faster?
What have you tried that didn't work?

David
> _______________________________________________
> dev-gaia mailing list
> dev-...@lists.mozilla.org
> https://lists.mozilla.org/listinfo/dev-gaia

JOSE MANUEL CANTERA FONSECA

unread,
Feb 10, 2013, 12:33:10 PM2/10/13
to David Flanagan, dev-...@lists.mozilla.org
El 09/02/13 00:55, "David Flanagan" <dfla...@mozilla.com> escribió:

>Thanks Kyle! This is an excellent idea.
>
>Here are my performance anecdotes:
>
>For gallery performance, I think I'm going to be abandoning indexedb
>entirely. The way my app is structured, it keeps an in-memory copy of
>all of its data anyway, and I never need to do complicated queries on
>it, so I've realized that I can store that data in a flat file and read
>the file at startup. It looks like I can read that file in about the
>same time it takes to just open the database, so this is a big win in my
>case.

I think we should push forward indexedDB performance improvements instead
of ad-hoc custom home-made solutions. The problem with approaches like
those you are describing is that every different app will end up defining
its own mechanisms. If indexedDB perf cannot be improved maybe managing
this kind of app data R/W files, can be abstracted by a library to be used
by different apps.

>
>With both IndexedDB and DeviceStorage, I've attempted to start the db
>opening process or the file reading process in a non-deferred script
>that is loaded first. This is in the hopes that by the time the document
>all the deferredscripts are loaded and I get a DOMContentLoaded, I'll
>have an open db waiting for queries, or I will have received my file
>from device storage. In practice, though, it appears that loading the
>document and its scripts take priority and that attempting to start my
>I/O earlier doesn't make things substantially faster.

Interesting, here we would need information from Gecko Platform people.
________________________________

Este mensaje se dirige exclusivamente a su destinatario. Puede consultar nuestra política de envío y recepción de correo electrónico en el enlace situado más abajo.
This message is intended exclusively for its addressee. We only send and receive email on the basis of the terms set out at:
http://www.tid.es/ES/PAGINAS/disclaimer.aspx

Kyle Machulis

unread,
Feb 11, 2013, 4:51:46 PM2/11/13
to dev-...@lists.mozilla.org
On Sun, Feb 10, 2013 at 9:33 AM, JOSE MANUEL CANTERA FONSECA <jm...@tid.es>wrote:

> I think we should push forward indexedDB performance improvements instead
> of ad-hoc custom home-made solutions. The problem with approaches like
> those you are describing is that every different app will end up defining
> its own mechanisms. If indexedDB perf cannot be improved maybe managing
> this kind of app data R/W files, can be abstracted by a library to be used
> by different apps.
>
>
I totally agree here, and I don't think this initiative is really a
perma-bandaid idea, but I also think we're going to see apps defining
mechanisms no matter what so we're trying to provide a set of proven
patterns as early as possible. I was mainly using IndexedDB as our example
for the moment because it's a well known issue, but we've also got issues
in layout, graphics, sound, etc... that could probably use these patterns.
I think having them established will also provide guidance people develop
wrapper libraries for things that aren't getting the performance gains we
require in the time we need, like you mentioned above.

Kevin Grandon

unread,
Feb 11, 2013, 6:36:07 PM2/11/13
to Kyle Machulis, dev-...@lists.mozilla.org
"Don't slow down the UI for remote operations."

One performance tip that might help is around creating a local cache when dealing with remote backends. This is basically baked into a few mobile frameworks like Sencha, and email does this in some ways. The basic idea is:

- Create a local datastore to operate on - IndexedDB should be fine.
- Keep the UI responsive to this local datastore
- Handle remote CRUD operations outside of your UI thread (perhaps a worker)

Example: The user creates some record. Your app should save the new record to the local cache, then show the UI immediately. A worker thread would actually be doing the work to sync that object, and not slow down the UI.

I'm not sure how useful this will be as an official pattern as it's fairly specific. Also maybe appcache could have some role here, but I haven't played around with it enough yet to know. I'm curious to hear feedback on this and if you guys think it's worth building a pattern around.

Thanks,
Kevin

Lucas Adamski

unread,
Feb 12, 2013, 12:07:08 AM2/12/13
to JOSE MANUEL CANTERA FONSECA, dev-gaia, dev-b2g, David Flanagan
On Feb 10, 2013, at 9:33 AM, JOSE MANUEL CANTERA FONSECA wrote:

> El 09/02/13 00:55, "David Flanagan" <dfla...@mozilla.com> escribió:
>
> I think we should push forward indexedDB performance improvements instead
> of ad-hoc custom home-made solutions. The problem with approaches like
> those you are describing is that every different app will end up defining
> its own mechanisms. If indexedDB perf cannot be improved maybe managing
> this kind of app data R/W files, can be abstracted by a library to be used
> by different apps.

Optimization beyond the 80% case is always an app-specific exercise. No doubt indexDB performance can be improved, but it won't be the right solution for all problems. I'm willing to state that media for example should be always saved directly to disk (even thumbnails).

Flatfiles are a perfect good solution for situations where reading performance is paramount, datasets are relatively small and updates are rare.

The first half of my career was data processing, including design, implementation and operations of the entire logging and processing system for a very large early search engine company. We always used flatfiles. I spent a year researching various "next generation" database options... and went back to using flat-files. Nothing else came close for performance and storage density.

To be clear, I'm not arguing against framework abstractions, but boilerplate & frameworks will rarely get you past the 80% threshold.

I think this discussion needs to happen because we can't individually keep reinventing the wheel. Its clear basic performance patterns are not currently well understood across the board.

For example, email seems to take forever loading what seems like my entire inbox. What's the most common use-case for an email app? Probably open, scan the last 5-10 new emails, maybe reply to one, and close again. We should optimize for that via incremental loading with read-ahead.

Read only the first two to five pages worth of data, and more as the user starts scrolling to keep ahead of the view. We don't need to keep loading if the user isn't scrolling. Even if the view sometimes has to catch up, its a better optimization than wasting battery, memory and the user's time loading a ton of data. 99% of which the user will not look at. These are patterns that other mobile platforms have already built in, and we are reinventing them from scratch unfortunately.

For each app this will be slightly different though, depending on the database access patterns and type of data stored. This is why David Flanagan's and Kyle's experiments of determining performance impact of different patterns are important. We should understand break-even points between using cursors, using getAll() for loading chunks (X to Y rows at a time), and loading the entire dataset. Sometimes getting the entire dataset from indexdb in one request makes the most sense, when that data is of a small bound size and tends to change often (i.e. a top 10 list). OTOH, atomic data that needs to be read quickly but never changes has no place in a database.

>>
>> With both IndexedDB and DeviceStorage, I've attempted to start the db
>> opening process or the file reading process in a non-deferred script
>> that is loaded first. This is in the hopes that by the time the document
>> all the deferredscripts are loaded and I get a DOMContentLoaded, I'll
>> have an open db waiting for queries, or I will have received my file
>> from device storage. In practice, though, it appears that loading the
>> document and its scripts take priority and that attempting to start my
>> I/O earlier doesn't make things substantially faster.
>
> Interesting, here we would need information from Gecko Platform people.

A useful pattern here can be breaking up your app into multiple iframes and not having everything hanging off the top level window. For one thing, it allows for better garbage collection as the iframes get periodically flushed, but also it means you don't have to block startup on loading tons of scripts. I think Andreas already brought this up, but really we probably should never have the top level window of an app contain anything more than a simple bootstrap and an iframe. In fact the top level window should probably never source any script, period.
Lucas.

Andrew Sutherland

unread,
Feb 12, 2013, 3:40:02 AM2/12/13
to dev-...@lists.mozilla.org
On 02/12/2013 12:07 AM, Lucas Adamski wrote:
> For example, email seems to take forever loading what seems like my entire inbox. What's the most common use-case for an email app? Probably open, scan the last 5-10 new emails, maybe reply to one, and close again. We should optimize for that via incremental loading with read-ahead.
>
> Read only the first two to five pages worth of data, and more as the user starts scrolling to keep ahead of the view. We don't need to keep loading if the user isn't scrolling. Even if the view sometimes has to catch up, its a better optimization than wasting battery, memory and the user's time loading a ton of data. 99% of which the user will not look at. These are patterns that other mobile platforms have already built in, and we are reinventing them from scratch unfortunately.

E-mail already does all this. If you open your Inbox that has already
been synced in the last hour, or while navigator.onLine returns false
(which gets broken if Marionette is enabled on your device, apparently),
the 15 most recent messages known to the device will be displayed and
then a refresh sync will be performed. Then incremental fetches will be
queued in the background immediately and as you scroll to maintain a
minimum padding of 2 screens worth of messages.

Unfortunately, most of the time the refresh mode of operation does not
fire. Early on, operating on my understanding of our UX goals and
consultation with UX, I implemented an additional sync heuristic that in
order to not jerk the user around by either showing them old messages
and having them need to scroll through a ton of messages to get to the
new ones, or showing them old messages and then instantaneously warping
them when the sync completes, we would first require some server sync to
complete first. The idea was that periodic background sync should help
avoid that heuristic needing to trigger all the time. (Unfortunately,
although initial implementation of periodic background sync happened, we
out-of-scoped it because the notifications and such were going to be
trouble.)

That clearly turns out to have been a bad call, so I've been correcting
the problem on bug https://bugzilla.mozilla.org/show_bug.cgi?id=822882.
As a heads-up, when that lands, you are going to potentially need to
scroll through a boatload of messages to get to the newest ones unless
we add some additional UI affordances.

The other big problem of course is just e-mail's startup time because of
all the code we load. Thanks to the JS team's improvements to JS source
compression (we were taking 1.1 seconds to re-compress the extracted JS
source from the compressed app zip) and Gaia team work to minify things
so the JS engine has less work to do, e-mail app startup prior to when
the database becomes an issue is also being improved. And we have other
plans too, but new/re-prioritized features are getting thrown at us much
faster than engineering manpower is.

Andrew

PS: The e-mail app even goes so far as to consolidate headers and bodies
into blocks to leverage the fact that we are using Snappy compression on
a per-key/value basis and in recognition of the fact that our IndexedDB
is backed by SQLite which is page-centric with a 32k page-size as
configured.

Julien Wajsberg

unread,
Feb 12, 2013, 12:38:09 PM2/12/13
to Kyle Machulis, dev-...@lists.mozilla.org

Don't forget most DOM operations return live lists (for exemple:
getElementsByTagName, querySelectorAll, properties children,
childNodes). This means for example that it is quite costly to merely
get the length.

So in your iterations for such lists, you should cache the length. I
made a jsperf case to prove that : http://jsperf.com/livelist-length.

Also, if you're merely checking the length of the children property (for
example) to know if a list is empty, you should check for a
firstElementChild instead, because computing the length of a live list
has a linear time.

This is certainly not important in most cases but this can be where you
have big lists of stuff.

--
Julien


Le 08/02/2013 20:27, Kyle Machulis a �crit :

Rick Waldron

unread,
Feb 12, 2013, 1:44:06 PM2/12/13
to Julien Wajsberg, Kyle Machulis, dev-...@lists.mozilla.org
On Tue, Feb 12, 2013 at 12:38 PM, Julien Wajsberg <jwaj...@mozilla.com>wrote:

>
> Don't forget most DOM operations return live lists (for exemple:
> getElementsByTagName, querySelectorAll, properties children,
> childNodes). This means for example that it is quite costly to merely
> get the length.
>

querySelectorAll is not a live NodeList

Also, If DOM node selection is still a performance issue, then it should be
filed against Gecko. Same goes for DOM node manipulation and creation.

Be careful, |children| and |childNodes| are not the same list. |children|
is nodeType = 1 and |childNodes| includes every type of node.


>
> So in your iterations for such lists, you should cache the length. I
> made a jsperf case to prove that : http://jsperf.com/livelist-length.
>

Slice it to an array:

var nodes = [].slice.call( document.querySelectorAll("selector") );

http://jsperf.com/livelist-length/2




>
> Also, if you're merely checking the length of the children property (for
> example) to know if a list is empty, you should check for a
> firstElementChild instead, because computing the length of a live list
> has a linear time.
>

Two techniques, but they yield varying results by browser:

1. slice the node.children
2. node.querySelectorAll("tag") (If you know the tag, ie. the list elem
case)


>
> This is certainly not important in most cases but this can be where you
> have big lists of stuff.
>

Important, but not as important as manipulation and creation... which can
have detrimental impacts.

Rick

Julien Wajsberg

unread,
Feb 13, 2013, 4:54:27 AM2/13/13
to Rick Waldron, Kyle Machulis, dev-...@lists.mozilla.org
Le 12/02/2013 19:44, Rick Waldron a �crit :
>
>
>
> On Tue, Feb 12, 2013 at 12:38 PM, Julien Wajsberg <jwaj...@mozilla.com
> <mailto:jwaj...@mozilla.com>> wrote:
>
>
> Don't forget most DOM operations return live lists (for exemple:
> getElementsByTagName, querySelectorAll, properties children,
> childNodes). This means for example that it is quite costly to merely
> get the length.
>
>
> querySelectorAll is not a live NodeList

my bad, you're right.
But computing the length still seems to be linear !

>
> Also, If DOM node selection is still a performance issue, then it should
> be filed against Gecko. Same goes for DOM node manipulation and creation.
>
> Be careful, |children| and |childNodes| are not the same list.
> |children| is nodeType = 1 and |childNodes| includes every type of node.

exactly :)
so we really want |children| most of the time.

>
> So in your iterations for such lists, you should cache the length. I
> made a jsperf case to prove that : http://jsperf.com/livelist-length.
>
>
> Slice it to an array:
>
> var nodes = [].slice.call( document.querySelectorAll("selector") );
>
> http://jsperf.com/livelist-length/2

wow, I knew it could be faster, but not _so_ faster.
Let's use this.


>
>
> Also, if you're merely checking the length of the children property (for
> example) to know if a list is empty, you should check for a
> firstElementChild instead, because computing the length of a live list
> has a linear time.
>
>
> Two techniques, but they yield varying results by browser:
>
> 1. slice the node.children
> 2. node.querySelectorAll("tag") (If you know the tag, ie. the list elem
> case)
>
>
>
> This is certainly not important in most cases but this can be where you
> have big lists of stuff.
>
>
> Important, but not as important as manipulation and creation... which
> can have detrimental impacts.

I don't know any other general rules than "avoid synchronous reflows, so
avoid querying the DOM while you're constructing it". Do you have some ?

--
Julien

Julien Wajsberg

unread,
Feb 13, 2013, 4:57:29 AM2/13/13
to Rick Waldron, Kyle Machulis, dev-...@lists.mozilla.org
Le 13/02/2013 10:54, Julien Wajsberg a �crit :
> Le 12/02/2013 19:44, Rick Waldron a �crit :
>>
>>
>>
>> On Tue, Feb 12, 2013 at 12:38 PM, Julien Wajsberg <jwaj...@mozilla.com
>> <mailto:jwaj...@mozilla.com>> wrote:
>>
>>
>> Don't forget most DOM operations return live lists (for exemple:
>> getElementsByTagName, querySelectorAll, properties children,
>> childNodes). This means for example that it is quite costly to merely
>> get the length.
>>
>>
>> querySelectorAll is not a live NodeList
>
> my bad, you're right.
> But computing the length still seems to be linear !

Ok, maybe not linear after all, indeed I don't know.

As shown by http://jsperf.com/livelist-length/4 the same penalty happens
to normal arrays. I really thought merely asking for the length on an
array was not a big deal anymore, but it seems it still is.

THanks Rick !
--
Julien

Julien Wajsberg

unread,
Feb 13, 2013, 7:50:02 AM2/13/13
to Rick Waldron, Kyle Machulis, dev-...@lists.mozilla.org
Le 12/02/2013 19:44, Rick Waldron a �crit :

>
> So in your iterations for such lists, you should cache the length. I
> made a jsperf case to prove that : http://jsperf.com/livelist-length.
>
>
> Slice it to an array:
>
> var nodes = [].slice.call( document.querySelectorAll("selector") );
>
> http://jsperf.com/livelist-length/2

As said in Twitter, actually my previous approach seems faster if we
take the slicing into account :

http://jsperf.com/livelist-length/5

--
Julien

Rick Waldron

unread,
Feb 13, 2013, 11:07:39 AM2/13/13
to Julien Wajsberg, Kyle Machulis, dev-gaia
On Wed, Feb 13, 2013 at 4:54 AM, Julien Wajsberg <jwaj...@mozilla.com>wrote:

> Le 12/02/2013 19:44, Rick Waldron a écrit :
> >
> >
> >
> > On Tue, Feb 12, 2013 at 12:38 PM, Julien Wajsberg <jwaj...@mozilla.com
> > <mailto:jwaj...@mozilla.com>> wrote:
> >
> >
> > Don't forget most DOM operations return live lists (for exemple:
> > getElementsByTagName, querySelectorAll, properties children,
> > childNodes). This means for example that it is quite costly to merely
> > get the length.
> >
> >
> > querySelectorAll is not a live NodeList
>
> my bad, you're right.
> But computing the length still seems to be linear !


> >
> > Also, If DOM node selection is still a performance issue, then it should
> > be filed against Gecko. Same goes for DOM node manipulation and creation.
> >
> > Be careful, |children| and |childNodes| are not the same list.
> > |children| is nodeType = 1 and |childNodes| includes every type of node.
>
> exactly :)
> so we really want |children| most of the time.
>

I'd go so far as to say that if, at any point, we encounter a common enough
use case for childNodes (that can't be covered by something else)... it
should be documented in big bold letters somewhere, because it's rare :)



> >
> > So in your iterations for such lists, you should cache the length. I
> > made a jsperf case to prove that : http://jsperf.com/livelist-length
> .
> >
> >
> > Slice it to an array:
> >
> > var nodes = [].slice.call( document.querySelectorAll("selector") );
> >
> > http://jsperf.com/livelist-length/2
>
> wow, I knew it could be faster, but not _so_ faster.
> Let's use this.
>
>
Forgot to add contextual reference: this is the technique we use in jQuery
(ie. Sizzle, the selector machine)


> >
> >
> > Also, if you're merely checking the length of the children property
> (for
> > example) to know if a list is empty, you should check for a
> > firstElementChild instead, because computing the length of a live
> list
> > has a linear time.
> >
> >
> > Two techniques, but they yield varying results by browser:
> >
> > 1. slice the node.children
> > 2. node.querySelectorAll("tag") (If you know the tag, ie. the list elem
> > case)
> >
> >
> >
> > This is certainly not important in most cases but this can be where
> you
> > have big lists of stuff.
> >
> >
> > Important, but not as important as manipulation and creation... which
> > can have detrimental impacts.
>
> I don't know any other general rules than "avoid synchronous reflows, so
> avoid querying the DOM while you're constructing it". Do you have some ?
>

Nicole Sullivan published a great resource:
http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/

Rick



>
> --
> Julien
>

Rick Waldron

unread,
Feb 13, 2013, 11:15:07 AM2/13/13
to Julien Wajsberg, Kyle Machulis, dev-gaia
On Wed, Feb 13, 2013 at 7:50 AM, Julien Wajsberg <jwaj...@mozilla.com>wrote:

> Le 12/02/2013 19:44, Rick Waldron a écrit :
>
> >
> > So in your iterations for such lists, you should cache the length. I
> > made a jsperf case to prove that : http://jsperf.com/livelist-length
> .
> >
> >
> > Slice it to an array:
> >
> > var nodes = [].slice.call( document.querySelectorAll("selector") );
> >
> > http://jsperf.com/livelist-length/2
>
> As said in Twitter, actually my previous approach seems faster if we
> take the slicing into account :
>
> http://jsperf.com/livelist-length/5



Interesting—I want to see what the Sizzle crew thinks of this; they collect
data to accompany scenarios for selector matching and nodelist operations
to fine tune Sizzle for optimal performance.

I'll report back.

Rick






>
>
> --
> Julien
>

Julien Wajsberg

unread,
Feb 20, 2013, 9:09:22 AM2/20/13
to dev-...@lists.mozilla.org
A follow-up to this :

Le 12/02/2013 18:38, Julien Wajsberg a écrit :
>
> Don't forget most DOM operations return live lists (for exemple:
> getElementsByTagName, querySelectorAll, properties children,
> childNodes). This means for example that it is quite costly to merely
> get the length.
>
> So in your iterations for such lists, you should cache the length. I
> made a jsperf case to prove that : http://jsperf.com/livelist-length.

Actually, in iterations in lists of same-depth-elements (eg: all
children of a specific node), what you should really do is _not_ using
index to access the elements, but rather iterate using nextElementSibling :

for (var cur = elts[0]; cur; cur = cur.nextElementSibling) {
// do stuff with cur;
}

or

var cur = parent.firstElementChild;
while (cur) {
// do stuff with cur;
cur = cur.nextElementSibling;
}


see http://jsperf.com/livelist-length/8

Regards,
--
Julien

Rick Waldron

unread,
Feb 20, 2013, 9:36:09 AM2/20/13
to Julien Wajsberg, dev-gaia
That is a *hot jam* indeed.

How would I get started creating a wiki page for these sort of guides?

Rick





>
> see http://jsperf.com/livelist-length/8
>
> Regards,
> --
> Julien

Schalk Neethling

unread,
Feb 20, 2013, 10:00:19 AM2/20/13
to dev-...@lists.mozilla.org
Hey Rick,

I guess one of the first questions would be where is the best place to
put this. I am thinking that most, if not all, of the items mentioned in
this thread relates to JS in general not not just in relation to Firefox
OS or perhaps even just mobile development so, we might very well put
this under the rest of the JS documentation on MDN i.e.
https://developer.mozilla.org/en-US/docs/JavaScript/Guide

Thoughts?
--
Kind Regards,
Schalk Neethling
Front-End Engineer
Mozilla
0 new messages