<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Songbird Blog &#187; performance development 1.0</title>
	<atom:link href="http://blog.songbirdnest.com/tag/performance-development-10/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.songbirdnest.com</link>
	<description>Play music. Play the Web.</description>
	<lastBuildDate>Thu, 09 Feb 2012 00:55:55 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Performance Improvements for 1.0</title>
		<link>http://blog.songbirdnest.com/2008/11/26/performance-improvements-for-10/</link>
		<comments>http://blog.songbirdnest.com/2008/11/26/performance-improvements-for-10/#comments</comments>
		<pubDate>Thu, 27 Nov 2008 01:42:20 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[performance development 1.0]]></category>

		<guid isPermaLink="false">http://blog.songbirdnest.com/?p=690</guid>
		<description><![CDATA[Optimization requires taking a step back to observe, measure, and experiment. Somehow it seems like a luxury, and features or bug fixes are always the priority. This release though, thanks to the survey and GetSatisfaction feedback you&#8217;ve been sending, speed was a primary focus. dbradley, pvh, jacklu, kreeger and myself had a lot of fun [...]]]></description>
			<content:encoded><![CDATA[<p>Optimization requires taking a step back to observe, measure, and experiment. Somehow it seems like a luxury, and features or bug fixes are always the priority. This release though, thanks to the survey and GetSatisfaction feedback you&#8217;ve been sending, speed was a primary focus.  </p>
<p>dbradley, pvh, jacklu, kreeger and myself had a lot of fun working on performance problems that had been annoying us. Our goal was to improve basic every-day library management, and the results are a huge step forward.</p>
<p>Scrolling is much much smoother, searching is thousands of times faster, databases are 60% smaller, and importing is nearly twice as fast and many times more efficient.  Songbird is much more pleasant to use, and can now easily handle 20,000+ track libraries.  In fact, it seems to scale about as well as other popular players.</p>
<h4>OBLIGATORY INFOPORN</h4>
<p><a href="http://files.songbirdnest.com/wp-content/uploads/2008/11/graph.png"><img src="http://files.songbirdnest.com/wp-content/uploads/2008/11/graph.png" alt="" width="500" height="468" class="alignnone size-full wp-image-691" /></a></p>
<p>This is (mostly) the output from a new performance lab that marcc and toks set up.  Every night we get performance reports for the library API, allowing us to verify progress and avoid regressions.  Don&#8217;t worry about the times, these are stress tests, not actual user use-cases. The lower the time, the better the performance.  </p>
<p>Read on if you&#8217;re interested in how these improvements were made.<br />
<span id="more-690"></span></p>
<h4>SCROLLING</h4>
<p>The main library view is a XUL Tree widget, just like the bookmarks in Firefox and mail view in Thunderbird, so we were concerned that Songbird used nearly twice as much CPU and scrolled much more slowly than these applications. Simply moving the mouse in a circle over the tree would consume up to 80% CPU.</p>
<p>Profiling with <a href="http://developer.apple.com/tools/sharkoptimize.html">Shark</a> showed a lot of wasted effort reading item metadata and firing events while painting the tree.  A little work to adjust how the selection is maintained and refactor the caching code significantly improved library scrolling.</p>
<p>Next was a paging hiccup every 1000 items when scrolling large libraries. Logging and timing queries showed that secondary sorting was to blame (first sort by artist, then by album, then by disc number and track number).  Getting sorted media item IDs for 1000 rows could require hundreds of queries, each with a cost proportional to the library size. </p>
<p>Since secondary sorting is not user configurable we now pre-compute another sort value for each property. For example, when album metadata for a track changes, the secondary sort value for the artist property is rebuilt and indexed, making sorting by artist, album, disc, track number essentially free.</p>
<h4>SEARCH + FILTERING</h4>
<p>In 0.7 we moved to <a href="http://ft3.sourceforge.net/">SQLite Full Text Search (FTS3)</a>, and found that in most cases it was extremely fast. Unfortunately though search time grew exponentially with the number of match results, and searching for &#8220;a&#8221; in large library could hang for up to 30 seconds.  Lame.</p>
<p>Half of the problem was the secondary sorting issue that had been slowing down scrolling.  Each query had to include the search and filter constraints, so a slight cost increase would be multiplied by 50 to 100x. Removing this multiplier helped a great deal.</p>
<p>The other half turned out to be the way we were joining the FTS3 table to the properties. Query optimization is mysterious, so testing and measurement are critical.   Query logging, our performance test harness, and the SQLite &#8216;EXPLAIN QUERY PLAN&#8217; command helped us determine that joining the FTS table, then the properties, then the media items is an order of magnitude faster than equivalent queries.</p>
<p>Finally, since library filter lists only show a small number of values, we now fetch all rows at once, removing the need for a separate count query of approximately the same cost.   </p>
<p>Lessons learned:</p>
<ul>
<li>Table joining order is extremely important.  As a rule, join the most restricting or most expensive to join table first. Whatever you do, test a few permutations. </li>
<li>Joining with virtual and temporary tables prevents the use of an index for both joining and sorting.   &#8216;EXPLAIN QUERY PLAN&#8217; shows that an index can still help with a join, but sorting has to be done the hard way. </li>
<li>Occasionally the query optimizer will make bad decisions, and forcing it to use a specific index can help quite a bit. Placing the unary plus operator (+) in front of a column name indicates that an index should not be used.  </li>
</ul>
<p>Recommended links:</p>
<ul>
<li><a href="http://www.sqlite.org/optoverview.html">SQLite Query Optimizer Overview</a></li>
<li><a href="http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html">SQLite Optimization FAQ</a></li>
<li><a href="http://katastrophos.net/andre/blog/2007/01/04/sqlite-performance-tuning-and-optimization-on-embedded-systems/">SQLite performance tuning and optimization on embedded systems</a></li>
</ul>
<h4>ADDING TRACKS</h4>
<p>Importing large libraries was slow, and required an obscene amount of memory.  With 1.0 it still isn&#8217;t ideal, but it&#8217;s fast, and the memory usage is 10% of what it was in 0.7.  Some of the improvement came from the database changes described below, but significant progress was made simply by avoiding unnecessary work.</p>
<p>The first pass removed unnecessary code paths.  0.7 computed an MD5 hash for every track, doubling IO costs, and would frequently loop over and copy the list of tracks.  This behaviour was redundant, and required a huge amount of memory when importing 50k+ tracks. </p>
<p>The second pass cleaned up library change listeners.  Tracing with the <a href="http://blogs.sun.com/brendan/entry/dtrace_meets_javascript">DTrace JavaScript probes</a> and profiling with <a href="http://www.hacksrus.com/~ginda/venkman/profiles/">Venkman</a> revealed that the smart playlist system and album art scanner ignored batching hints.  Batch notification was added after the initial listener API was created, and for backwards compatibility it is opt-in, making it easy to forget that a single JavaScript listener called 50,000 times can hang the UI.   The listeners were fixed, but we need an API that is easier to use correctly.</p>
<h4>DATABASE ENGINE</h4>
<p>Experimenting with <a href="https://developer.mozilla.org/en/Debugging_memory_leaks">BloatView </a> and <a href="http://developer.apple.com/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Introduction/chapter_1_section_1.html">Instruments</a>, we established that the large library memory problems in 0.7 were mostly caused by our database engine. </p>
<p>Any time Songbird operates on a large set of media items (adding, deleting, enumerating) it builds multiple queries per item, enqueues them, and then executes them.  In 0.7 these queries were constructed as strings, which, multiplied by a library of 50k+ tracks, could require hundreds of megabytes. </p>
<p>The problem was solved by adding <a href="http://www.sqlite.org/c3ref/stmt.html">prepared statement</a> support to the database engine and library code.  Now a single compiled query instance can be used with a large set of parameters, and the number of media items involved is barely a factor. </p>
<p>Discoveries along the way:</p>
<ul>
<li>NS_ProxyRelease should be treated with caution, since it can cause objects to pile up.</li>
<li>A string query or prepared statement with a &#8220;column IN (?, ?, &#8230;)&#8221; clause is much faster than an equivalent set of &#8220;column = ?&#8221; prepared statements. </li>
<li>The SQLite &#8216;ANALYZE&#8217; command doesn&#8217;t need to be run all the time. Dump values from a typical database and include hardcoded results with the schema.</li>
</ul>
<h4>DATABASE SIZE</h4>
<p>The easiest way to improve database performance is to just have less data.  Seems obvious, but it&#8217;s easy to overlook.</p>
<p>In 0.7, a 10,000 track library required a 50MB database, of which 60% went to storing indices (according to the sqlite analyzer).  Indices store a complete copy of the indexed data, and are expensive to maintain.  They are also difficult to remove, as you have to be sure you wont hurt performance.  Using &#8216;EXPLAIN QUERY PLAN&#8217; to figure out which indices were used, and the performance test suite to avoid regressions, we dropped three indices for an overall size reduction of 40%.   </p>
<p>We also noticed that many values were stored as UTF-16, despite never containing Unicode (since our generic property schema stores everything as strings).  With a little experimentation we discovered that using UTF-8 databases did not impact performance, even for large libraries of entirely Unicode metadata.</p>
<p>Switching to UTF-8 gave another 40% reduction, and an average 10,000 track library in 1.0 is now 18MB.</p>
<h4>UP NEXT</h4>
<p>Where are the new pain points?  If you&#8217;ve been using the 1.0 RC builds please let us know how things are working, and where you think we should focus.  Songbird can never be &#8220;too fast&#8221; or &#8220;too lean&#8221; and we&#8217;re committed to improving performance in every release going forward.  </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.songbirdnest.com/2008/11/26/performance-improvements-for-10/feed/</wfw:commentRss>
		<slash:comments>50</slash:comments>
		</item>
	</channel>
</rss>

