Status update: November 6, 2009

  • Started looking again at making Places query APIs async, bug 490714. First need to tackle bug 499985, sorting of query results should be done entirely in SQL.
  • “Helped” bz with test failures in blocker bug 526178, binding loading order changed in 3.6b1 compared to previous version of firefox.
  • Awaiting Josh’s review on bug 506814, get rid of / Change GetPersistentDescriptor / SetPersistentDescriptor. Spoke to him last week, think he’s pretty busy with more important things.

  • Work on bug 499985.
  • Been thinking about the gap between the Places database and its query code and the janky structure that bridges it. Think more about it, with intensity.

Cold Ts

Alice ran the first cold Ts on her staging machines today. I ran it locally against today’s nightlies and produced these numbers:

Shiretoko (3.5)   10266.25 ms
Namoroka  (3.6)   10017.55
Minefield (trunk) 11023.65

If we had had cold Ts when we landed whatever caused Minefield’s 10% regression over Namoroka, we would have caught it.

Cold Ts is Ts run under simulated cold startup. Right now we’re able to simulate cold startup on OS X only, so the test is not yet switched on for Windows and Linux. The procedure on Linux, echo 3 > /proc/sys/vm/drop_caches, requires root privileges, which either opens a security hole on the Tinderboxen or forces a sudo prompt. Windows is worse: Nothing we know of works well or at all except for reboot, and the Talos infrastructure makes rebooting impractical. Any help here would be greatly appreciated.

Firefox startup 2

Another week in Firefox startup.

Good news is, joelr joined the platform team and is kicking startup in the shins. He’s on the ball, I hope he doesn’t mind my saying.

My OS X-specific XPT-linking patch from last time landed. Nightly trunk (Minefield) users on OS X, I’d like to hear from you if you notice an improvement in cold startup time. I was unable to, however, despite noting a good decrease in I/O. Ted is working on improving OS X packaging overall in bug 463605.

Making progress on setting up a Talos cold Ts. A byproduct is that we’re going to allow Talos tests to run head and tail scripts, which may be useful for others. Simulating a cold startup on Windows looks like it might be a little tricky, but other than that I don’t foresee any roadblocks. (What could go wrong?)

Posted a list of all files touched on OS X cold startup, including time spent in the associated syscalls, number of bytes touched, and throughput. You can drill down into each file to see all the I/O-related syscalls on it. This is data from one run only, so it’s not smoothed out across multiple runs. I have noticed some jitter between runs: For example, reading four bytes of search.json will take 200ms (!) during one run, but on most others it’s something like 10-20ms (!). The order of files by duration tends to remain fairly consistent, though. I’ll try to keep this up-to-date; already it’s several days old, and since then I’ve improved the output so that it shows the counts of each syscall on the main page. Feel free to rummage through my startup dump.

My daily startup notes, data, and scripts are open as always. For details and context, check there.

Startup

Some of us have recently been working on improving Firefox’s startup performance. I’m focusing on cold startup, which is heavily I/O bound. You can read about it in my chronicles of startup, which contain notes and thoughts, day-to-day travails, and some scripts for analysis that anybody can run to reproduce my results.

Yesterday I filed bug 510309 to combine XPT files for OS X DMG packages. It should shave a couple hundred milliseconds off of OS X cold startup time spent in I/O. Dietrich, Alice, and I are setting up a Talos Ts to measure cold startup, and that will provide more confidence in this number and more accurate and stable numbers in general.

Update: Jesse pointed out that percentages are helpful when talking about performance. I’ve been getting 8 to 9 wall clock seconds pretty consistently on OS X (simulated) cold startup. Taking the 200ms decrease in I/O (not wall clock time), which is toward the low end of some of the runs I’ve done, that’s a 2.5% improvement over 8s. In the bug linked above I note a decrease of 285ms, a 3.6% improvement. One big caveat is that I was unable to see a corresponding drop in wall clock time in my testing, which is one reason I’d like to get a Talos cold Ts set up.

Locale-sensitive collations in Storage

Storage on the Mozilla platform now supports locale-sensitive collations.

SQLite provides a few simple built-in collations: BINARY, NOCASE, and RTRIM, but as the first suggests they all use memcmp and ignore text encoding and the user’s locale. If you want to show respect to your user and the vagaries of her language’s collating conventions, you have to load your entire set of results and then sort it manually. Sweet.

But now you can do it all in SQL. Bug 499990 adds the following collations sensitive to the locale of your user’s application:

locale
Case- and accent-insensitive
locale_case_sensitive
Case-sensitive, accent-insensitive
locale_accent_sensitive
Case-insensitive, accent-sensitive
locale_case_accent_sensitive
Case- and accent-sensitive

That’s everything covered by the platform’s existing collation facilities.

Use them like so:

SELECT * FROM fooflefipples ORDER BY name COLLATE locale ASC;

Locale-sensitive collations are useful for everyone building on the platform, but I added them as part of some ongoing work on async Places APIs in Firefox. We’d like to notify consumers as batches of results load from the database, but that’s not possible if we have to sort the entire set outside the database — which in turn increases Places’s code size for something that should be handled at the platform level, and now it is.

For a nice summary of the importance and difficulty of sorting strings in our multilingual digital world, see the introduction to the Unicode Collation Algorithm specification.

In related Storage news, Curtis is adding a Levenshtein distance function. (Pretty cool, but my patch is way cooler, don’t tell anybody.)

Going Places quickly

Improved performance is a key goal of Firefox 3.5 and beyond. We have our work cut out for us on the Places team.

For example, if you have lots of bookmarks or history, you might have noticed that searching in the history sidebar or bookmarks library can be a little, uh, unresponsive. When you search or click a tag, folder, or history container in one of the sidebars or library, Firefox drops everything to fetch your results. I’ve been working on making these actions asynchronous so that Firefox can walk and chew gum.

Try out a test build here. It’s based off of Minefield, the trunk version of Firefox. Warning: You can use this with your default Firefox profile, but only if you normally use Firefox 3.0, the current 3.5 nightly, the current Minefield nightly, or 3.5 RC 1. (As always, however, it’s a good idea to backup your profile before you try any experimental build.) If you run any of the 3.5 betas up to and including beta 4, you will need to make a copy of your profile and use it instead.

This build loads searches and tag, history, and query containers asynchronously. Instead of becoming unresponsive as it loads the entire set of results, Firefox will display results batch by batch as it retrieves them. That’s the plan anyway. Open up the sidebars and library and give it a shot.

A few notes about what not to expect:

  • Responsiveness is still not 100%. Depending on the size of your history, searching and opening large containers remain a little jerky, especially the first time. Many disparate pieces impact responsiveness, and this one patch doesn’t touch them all. I hope you will notice some improvement, though, and I’d certainly like to hear if it makes things worse.
  • This is not a cure-all. It addresses only the issues described in this post, not things like the awesomebar or deleting large numbers of bookmarks.
  • Completeness and visual polish.

Follow along in bug 490714.

Places stats last call

A quick note to kindly request your help in making Firefox better. If you have not already done so, please take thirty seconds to visit the Places Stats Project and submit some anonymous statistics gathered from your Firefox bookmarks and history. This is a great, super-easy chance to contribute to the improvement of Firefox, so don’t miss it.

The Places Stats Project has been up and running for nearly three months now, and I previously blahhged about it and our initial results here. Everyone’s been busy getting Firefox 3.5 out the door since then, but now that work on it is winding down I’ll post soon about some of the things we learned.

Places stats

The Places team has realized that we don’t have a good idea of how people use bookmarks and history in Firefox. We don’t know if most people have lots of bookmarks, a few, or somewhere in between; lots of history or a little; large Places databases or small. The better we know how people use bookmarks and history, the better we can tweak performance.

So we started a project to collect statistics volunteered from people’s Places data. To be clear, we’re not talking about harvesting people’s individual bookmarks and history. What we’re interested in is, well, 1) getting volunteers, and 2) mostly numbers: the number of bookmarks people have, the number of pages in their histories, and so on. This raises reasonable privacy concerns, but I’m not going to address them here; the project site takes them on in detail.

What I will focus on here is how we built the data collection site and what we found from our initial round of volunteers.

Implementation

https://places-stats.mozilla.com/stats is a simple Ruby script that adds a data point on POST and dumps a big XHTML table on GET. We’re using MySQL on the backend (natch) and ActiveRecord as an ORM bridging Ruby and MySQL.

On POST /stats expects a string of JSON. From the front page of the site people can access a script, copy it to their clipboards, paste it into their JavaScript consoles, and in a pop-up window get back JSON that encodes their Places stats. Then they press a button to POST it. For the most part the property names in the JSON correspond directly to the columns of the main table in the stats database. (I’ll use the terms { "property name": "property value" }.) The columns themselves are the dimensions we’re measuring for each data point: number of bookmarks, file size of Places database, etc. The /stats script parses the JSON and makes sure that valid property names and values were specified, and from there it’s a simple insert into the database.

On GET /stats pulls in a table template using ERB, a Ruby-based templating system, and populates it appropriately. /stats can also output JSON on GET. It checks the Accept request header and if it contains application/json, it outputs a JSON object { "avg": AVG, "stddev": STDDEV, "max": MAX, "min": MIN }, where AVG, STDDEV, MAX, and MIN are themselves objects whose property names are the same as those when POSTing JSON and whose property values are the average, standard deviation, maximum, and minimum values collected so far for each dimension. In other words you get back the first four rows of this table in JSON form. Right now David is using the JSON to inform his database generation script. We could get fancier and enable more parameterized output, like, “get me the number of bookmarks for all data points.” We could also do different output types like CSV — this one at least is on the to-do list. I’m not sure yet what we will need for this project specifically, but we are aware that other people may find the data or even our methodology useful.

Initial round

We asked people at Mozilla to be our guinea pigs, and we got 127 unique responses in return. This data set is small, and there’s not much to draw from it, but uh, below are graphs illustrating some of the more interesting dimensions. Some people had many bookmarks, history visits, and so on, but overall the data skews the other direction.

There are two graphs for each dimension. The first simply shows all the data points, along with the mean and standard deviation, which is fairly big for all our data. The second graph is a histogram. Each y-axis tick label is the upper bound on the bucket it sits next to. The bucket’s lower bound is the next label below it. So for example the top bucket of the places_file_size graph contains about 2 data points ranging in file size from over 99 MB to 104 MB. The bottom bucket of that graph contains about 22 data points ranging in file size from over 0 MB to 5 MB.

places_file_size1

places_file_size-hist

moz_bookmarks_cnt

moz_bookmarks_cnt-hist

moz_historyvisits_cnt

moz_historyvisits_cnt-hist

moz_places_cnt

moz_places_cnt-hist

I had expected that some of these distributions would be approximately normal, but this measly data doesn’t quite bear that out, and I’m not very bright anyway. Maybe when we gather more data. But after thinking about it, I wonder whether the distributions will not generally be normal after all. To take one example, everybody starts out with just a handful of bookmarks after installing Firefox. (Same with history visits.) Some people will add 20 bookmarks a day, some people 20 bookmarks a month, others 20 bookmarks a year. It takes work to add bookmarks, and it takes more work to add more bookmarks. Some people will climb that hill (nerds), some won’t (pep-peps). But it seems reasonable to guess that the farther up the hill you go, the fewer people you’ll see. If that’s accurate it might suggest something with a shape like an exponential distribution. The question is, how far does the average person climb?

Some ideas for future analysis:

  • We could use clustering methods to come up with some exemplar Places database shapes, or in other words, archetypal Places users.
  • There are correlation questions we can ask, like, “What makes a large Places database?” “What determines the size of frecency_first_bucket_visit_cnt?” “Are some dimensions mutually dependent?” (Maybe these aren’t good examples; we know more or less the answers to them now, and I’m not sure they’re useful. But we might find something unexpected or just unthought of.)