Speeding Amarok up

by Rudd-O published 2007/10/05 15:25:47 GMT+0, last modified 2013-06-26T03:24:19+00:00

Amarok's collection is a godsend. The collection scanner isn't. It kills my carefully primed cache with lots of memory pages full of MP3 data. 20.000 MP3s will do that to you (not my actual song count). Therefore, I propose the following patch:

The patch

The patch is against TagLib svn, the library in charge of reading the MP3 tags and feeding them to Amarok:

Index: toolkit/tfile.cpp
===================================================================
--- toolkit/tfile.cpp   (revisión: 721576)
+++ toolkit/tfile.cpp   (copia de trabajo)
@@ -30,7 +30,11 @@
 # include <io.h>
 # define ftruncate _chsize
 #else
+ /* Rudd-O: for fadvise */
+ /* I have NO idea how to make this conditional on compile time.  Ideas? */
+ #define _XOPEN_SOURCE 600
  #include <unistd.h>
+ #include <fcntl.h>
 #endif
 #include <stdlib.h>

@@ -41,6 +45,7 @@
 # define W_OK 2
 #endif

+
 using namespace TagLib;

 class File::FilePrivate
@@ -73,6 +78,7 @@

 File::File(const char *file)
 {
+  int fadvise_retvalue = 0;
   d = new FilePrivate(::strdup(file));

   // First try with read/write mode, if that fails, fall back to read only.
@@ -87,12 +93,26 @@

   if(!d->file)
     debug("Could not open file " + String(file));
+
+  /* Rudd-O: we don't want the kernel to cache this file descriptor or perform any readahead -- but only if no one else opens the file.  see posix_fadvise(2) */
+  /* FIXME I have NO idea how to make this conditional on compile time.  Ideas? */
+  if(d->file) {
+    fadvise_retvalue = posix_fadvise(fileno(d->file),0,0,POSIX_FADV_RANDOM);
+    if (fadvise_retvalue != 0)
+      debug("Could not tell the kernel to not cache the file " + String(file));
+  }
 }

 File::~File()
 {
-  if(d->file)
+  int fadvise_retvalue = 0;
+  if(d->file) {
+    /* Rudd-O: FIXME Also needs to be conditional to the availability of posix_fadvise */
+    fadvise_retvalue = posix_fadvise(fileno(d->file),0,0,POSIX_FADV_DONTNEED);
+    if (fadvise_retvalue != 0)
+      debug("Could not tell the kernel to evict from cache the file " + String(file));
     fclose(d->file);
+  }
   delete d;
 }

The theory

posix_fadvise(), in theory, on POSIX systems (Linux included) lets you tell the system how to behave with regard to a certain file. In particular, this patch tells Linux that the MP3 will be accessed randomly.

Linux 2.6 obeys it by simply not performing readahead. In effect, the patch says "you shouldn't perform readahead whlie reading the tags on the MP3s".

When the file is closed, the patch also uses the POSIX_FADV_DONTNEED hint, to make Linux evict any memory used by the recently read file from the page cache (but only if no other process/thread has the file open, so it should be safe).

Why this should work

TagLib figures out the tags by reading two key parts of the MP3 file: the very end for ID3v1 (a couple hundred bytes), and the very beginning for ID3v2 (up to 64 K, but usually considerably less).

What TagLib does is:

  1. Open the file
  2. Read the data (a total of anywhere between 16K and 64K)
  3. Close the file

But the Linux kernel does a few magic performance tricks behind the scenes, which actually cause the following to happen:

  1. Open the file
  2. Read the data -- with block device and filesystem readahead, so maybe up to 512K will be read
  3. Put the data in the block memory cache
  4. Close the file
  5. Hold on to the data in the cache for as long as possible, maybe paging out some pages from the current working set

Now, under regular conditions, these performance tricks actually work. But in the context of a collection scan, it's brain damaged -- does your computer really need to extra data from thousands of files, and then keep that data in memory?

Since there is no need for readahead (at most eight or nine 8K blocks will be needed, and the read operation is explicit about that), we disable that. This should considerably reduce the I/O demands.

Since there is no need for caching the contents (what are the odds that you want to play a specific file in 15.000, seconds after it's been scanned?) we save memory by telling Linux not to cache the contents of the files. This should relieve memory pressure considerably, and preserve other things in memory (think application icons or code).

This patch should make scans faster and make your computer more usable while your music is being scanned or when Amarok is starting up, but...

I need benchmarks!

Who volunteers? I'll post them here!

Update: here's the corresponding bug report in the KDE bug tracking system.