<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5346954106731174402</id><updated>2012-02-16T00:32:10.164-08:00</updated><title type='text'>QINSB is not a Software Blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>18</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-5017381634296987475</id><published>2011-12-12T16:28:00.000-08:00</published><updated>2011-12-12T17:38:43.405-08:00</updated><title type='text'>Stack crawling in DrMemory without frame pointers or debug info</title><content type='html'>I work on DrMemory, a dynamic instrumentation tool for finding memory bugs in native apps similar to memcheck, more widely known as just Valgrind.  We have to be able to generate stack traces in a wide variety of interesting situations, and we want to do it quickly and without depending on slow, non-portable, and hard to integrate libraries.&lt;br /&gt;&lt;br /&gt;If you remember your early computer systems classes, you'll recall those exam questions about unwinding the stack by chasing the base pointer.  The reality is that for years we've been stuck with x86_32, which has a measly 8 general purpose registers, and compilers have been itching to grab that tender little %ebp register.  So, they took it, and now the world is much more complicated because walking the stack is a whole lot harder.&lt;br /&gt;&lt;br /&gt;To unwind the stack, we need to answer the question: given the stack pointer (SP) and program counter (PC), what is the offset from the SP to the next return address?  With that, we can repeat the question until we hit main (or NULL or whatever).&lt;br /&gt;&lt;br /&gt;Maintaining a huge table mapping any arbitrary module offset to a stack offset would waste a lot of space, so most systems use some form of state machine encoding that can compute the value on the fly, should you need to walk the stack.  (Win64 SEH uses an interesting mechanism involving carefully constructed prologues which I'd like to learn more about.)  If stack walking is rare, such as for an "exceptional" exception or when in the debugger, then the state machine (DWARF CFI) works great.  But if you need to do it all the time and you need to do it on multiple platforms, it will be slow, you will need platform-specific code, or you will need to depend on some other library.  Pulling arbitrary code into a tool that runs in the same as the address space as the application can often break isolation between the tool and the app, so we don't like to do it very often.&lt;br /&gt;&lt;br /&gt;However, we're in dynamic binary instrumentation land, so we get to see every instruction as it's executed.  The next most obvious solution for unwinding is what we call the "shadow stack" solution.  Every time you see a call or ret instruction, you just push or pop the PC on to or off of the current thread's shadow stack.  If you need to record lots of stack traces, then this may be your best bet, especially since you can imagine logging these updates and then recording a trace is as easy as logging a timestamp.  I believe that this is what ThreadSanitizer does, because they need to produce a stack trace not just for the current memory access, but for the other memory access that races with the current thread.&lt;br /&gt;&lt;br /&gt;For a tool like DrMemory, we only need to store stack traces for every heap routine invocation, which is not nearly quite as often.  We also don't want to unnecessarily slow down an app that performs many calls in a loop without calling any heap routines or creating any memory bug reports.  You also have to worry about a lot of corner cases, such as an app which clobbers the return addr to go somewhere else, or longjmp, or exception handling.&lt;br /&gt;&lt;br /&gt;The solution we use is I think quite elegant.  First, if there are frame pointers, we use them, because it's fast (like iterating a linked list that's in cache :).  While the compiler may get a 10% perf boost from an extra register, we get a bigger boost by not doing a "scan", which is what I am about to describe.&lt;br /&gt;&lt;br /&gt;If we don't have frame pointers, we start with the stack pointer, and scan each pointer-aligned cell for a value that looks like a pointer into executable memory.  This part is obvious, and you can get Windbg to do the same thing and symbolize it to boot (anyone know how to do this in gdb?).&lt;br /&gt;&lt;br /&gt;The problem is that most code rarely initializes its entire stack frame.  In any given frame, there is a lot of spill space for temporaries in unexecuted code, or arrays that are not filled.  This means that stale return addresses from prior frames are still on the stack.  The clever bit is realizing that we're already shadowing every memory write to track definedness information to find uninitialized reads, so we actually *know* which bytes are initialized and which are not.  That means we can tell what is a probable return address and what is stale.&lt;br /&gt;&lt;br /&gt;Still, what if the app stores a function pointer to the stack?  That will look like a code pointer and show up as an extra frame.  The next bit of cleverness is where we take the candidate return address and look backwards in the code for likely encodings of a call instruction.  This is pretty straightforward, as there are only a&amp;nbsp;handful of encodings for direct and indirect calls, and we can just enumerate the opcodes along with their offsets.  If it's a function pointer, this test should fail, unless we're really unlucky.&lt;br /&gt;&lt;br /&gt;And with that, we have our (mostly) reliable, (reasonably) performant, and (mostly) portable stack crawler!  :)  To my knowledge, Memcheck doesn't attempt any of this craziness, and encourages you to recompile with FPs if it gets a weird looking stack.&lt;br /&gt;&lt;br /&gt;Edit: &lt;a href="https://plus.google.com/u/1/108532745084733145449/posts/cGKcBrVBHZY"&gt;Comments on G+ are preferred&lt;/a&gt;, since I'll actually respond.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-5017381634296987475?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/5017381634296987475/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/12/stack-crawling-in-drmemory-without.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/5017381634296987475'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/5017381634296987475'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/12/stack-crawling-in-drmemory-without.html' title='Stack crawling in DrMemory without frame pointers or debug info'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-607565033745032582</id><published>2011-08-11T09:57:00.000-07:00</published><updated>2011-08-11T10:25:50.688-07:00</updated><title type='text'>Early injection of DBI frameworks on Linux</title><content type='html'>I just had an interesting discussion with Qin Zhao about how the various instrumentation frameworks out there achieve early injection.  I haven't actually cross-checked this all at all, so it may not be 100% accurate, but I thought it was interesting enough to write down.&lt;br /&gt;&lt;br /&gt;Early injection is the ability of a binary instrumentation framework to interpret an application from the very first instruction.  On Linux, applications do not start life in userspace at main.  They start life at _start, and that usually hops into the dynamic loader to bring in the shared libraries such as libc.  Usually this code is very similar for every application, and you don't need to instrument it to find uses of uninitialized memory in your application.  However, its possible you have an app that doesn't use the dynamic loader, or there really are bugs there you want to see.  A related problem is how to load your own dynamic libraries without conflicting with the loader being used by the application.  Typically the DBI framework will load two copies of libc: one for the application, and one for the framework and its libraries.  DynamoRIO, Valgrind, and Pin all have different ways of loading and injecting with different tradeoffs.&lt;br /&gt;&lt;br /&gt;DynamoRIO is the easiest, because we don't support early injection on Linux.  (Windows is a different story which I can't speak to.)  We simply set LD_LIBRARY_PATH and LD_PRELOAD in a shell script that wraps the application invocation.  This way, the loader will pull in one of our shared libraries, and call _init, at which point we take control.  Instead of returning back to the loader, we start interpreting from where we would have returned.  For DynamoRIO's dynamic libraries, the most important of which is the actual tool that you want to run, ie your race detector or memory debugger, we have our own private loader which keeps its own libraries on the side and does all the memory mapping and relocations itself.  This is a pain, since it practically reimplements all of the system loader, but we do have the ability to place restrictions on the kinds of libraries we load to make things easier.&lt;br /&gt;&lt;br /&gt;Valgrind is the next easiest, since it's basically the inverse of the above strategy.  The command "valgrind" is a proper binary, so the system loader kicks in and loads all of Valgrind's libraries for it.  It can use dlopen to bring in the tools it wants.  To load libraries for the application, Valgrind has a private loader similar to our own, except that it loads the application's libraries instead of Valgrind's.&lt;br /&gt;&lt;br /&gt;Finally, Pin has the most interesting strategy.  Pin does not have it's own loader.  Pin is also a proper binary.  They start by loading all the libraries they ever want, including the tool you want to run, using the system loader (dlopen, etc).  Then they fork a child process.  The *parent*, not the child, sets up a call to exec to match the invocation of the application.  Somehow, probably using ptrace or some other API, the child is able to pause the parent application before the first instruction and attach.  The child then copies its memory map into the parent.  If there are conflicts, it errors out.  The child then redirects execution to Pin's entry point, which eventually starts interpreting the application from _start.&lt;br /&gt;&lt;br /&gt;Pin's approach allows early injection without the maintenance burden of a private loader.  However, it can cause conflicts with the application binary and requires heuristics to set preferred addresses which won't conflict.  It also slows down startup, due to the extra forking and memory mapping and copying.  Personally, I think this is pretty cool, even if it has some transparency issues.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-607565033745032582?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/607565033745032582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/08/early-injection-of-dbi-frameworks-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/607565033745032582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/607565033745032582'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/08/early-injection-of-dbi-frameworks-on.html' title='Early injection of DBI frameworks on Linux'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-8135772538639578467</id><published>2011-05-05T12:39:00.000-07:00</published><updated>2011-05-07T09:50:01.581-07:00</updated><title type='text'>FLAGS dependencies on x86</title><content type='html'>&lt;div&gt;One big annoyance I have with x86 in my work on DynamoRIO is the special FLAGS register.  You can't read or write to it like a normal register, and 2/3 of the bits are reserved or always 0 or 1.  The only easy way to get flags in or out is to use pushf and popf, but in DynamoRIO we can't use those without switching off the application stack, since it could be doing things like using the x86_64 Linux red zone.  So, we try to avoid clobbering FLAGS in inserted code whenever possible.  For example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You wanted to subtract a constant?  Instead of "sub $-1, %rax" try "lea -1(%rax), %rax".  Works for add, too.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With the more complex base, displacement, index, scale, and offset form of the x86 memory operand, you can get even more fancy, accomplishing small shifts and multiplies and register to register adds using the LEA instruction.    You can even get a 3-operand add like so:&lt;/div&gt;&lt;div&gt;lea 0(%rax, %rbx, 1), %rcx&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Which adds %rax and %rbx and stores the result in %rcx.  Another old trick is multiplication by 5, which works like so:&lt;/div&gt;&lt;div&gt;lea 0(%rax, %rax, 4), %rax&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This is powerful enough to perform register to register adds and simple arithmetic with constants, but we can't yet subtract a register from another.  Unfortunately, SUB and NEG both touch FLAGS.  However, the NOT instruction does not.  This means, assuming %rax is dead, we can rewrite "sub %rax, %rbx" like so:&lt;/div&gt;&lt;div&gt;not %rax&lt;/div&gt;&lt;div&gt;lea 1(%rax, %rbx, 1), %rbx&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Remembering how two's complement works, negation is equivalent to a complement and add one.  Addition is commutative, so we can fold the increment into the displacement field of the LEA.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Finally, you can also use JRCXZ (JECXZ in 32-bit land) to perform a jump if RCX is zero, without looking at flags.  Putting it all together, you can implement equality checks without changing flags like so (assuming one of %rdi or %rsi and %rcx are dead or have been saved):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Flags code:&lt;/div&gt;&lt;div&gt;cmp %rdi, %rsi&lt;/div&gt;&lt;div&gt;je .Llabel&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;No flags code:&lt;/div&gt;&lt;div&gt;&lt;div&gt;not %rsi&lt;/div&gt;&lt;div&gt;lea 1(%rdi, %rsi, 1), %rcx&lt;/div&gt;&lt;div&gt;jrcxz .Llabel&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I haven't checked, but I imagine the compare is faster.  However, in DynamoRIO, we have to save the flags for the cmp, which requires three instructions to save and to restore.  With that factored in, I have a feeling the no flags version may be preferable.  I will have to implement the no-flags transformation and see if it performs better.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unfortunately, the toy client I am developing with has code like this:&lt;/div&gt;&lt;div&gt;lea -1(%rdx), %eax&lt;/div&gt;&lt;div&gt;test %rax, %rdi&lt;/div&gt;&lt;div&gt;jz .Llabel&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Which performs an alignment check (assuming rdx is a power of two).  I'm not sure how to implement an arithmetic AND (which is what TEST does) without touching flags.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-8135772538639578467?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/8135772538639578467/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/05/flags-dependencies-on-x86.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8135772538639578467'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8135772538639578467'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/05/flags-dependencies-on-x86.html' title='FLAGS dependencies on x86'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-4721579343798009065</id><published>2011-04-06T15:39:00.000-07:00</published><updated>2011-04-06T16:37:08.249-07:00</updated><title type='text'>HipHop talk</title><content type='html'>&lt;div&gt;I just went to a talk by Facebook HipHop engineer Haiping Zhao about various systems issues FB has had.  There was a discussion of language issues with data movement in distributed systems and how to get that right, but I'm going to focus on HipHop, Facebook's open source PHP to C++ translator, since that's more up my alley.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thinking carefully about HipHop, it seems to make better tradeoffs for large web deployments than the (at this point) traditional JIT approach.  HipHop essentially trades compilation time, code size, and maximum performance for performance predictability and immediate startup performance.  Furthermore, static compilation is probably perceived as being less likely to have bugs than a JIT compilation to someone in charge of deploying the system, although I would argue against that perception. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The main argument against static compilation of dynamic languages is that you don't have enough information to generate efficient code, and that you need to be able to run it before compiling it.  To address that, HipHop is uses feedback directed (aka profile guided) optimization, even though PGO has never caught on in the C/C++ world.   Essentially, they translate all their PHP to instrumented C++ to get types of variables, and then run that on an instance with a model workload.  The compile takes about 15 minutes to make a 1 GB binary (the gold linker helps), and the profiling takes about another 15 minutes.  Then they re-do the compilation with the profile information.  This allows optimizations like speculating the target of a call, and speculating the type of a variable as being int or string, the easy to optimize types.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I would argue that this approach won't have as good peak performance as a JIT, which only has to generate the specialized version of the code, and can do some more clever optimizations.  Also, a JIT can always fallback to the interpreter for the general case.  If your profiling ends up being wrong (unlikely), a JIT can respond appropriately.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The drawback of the JIT is that you don't know when or if your code is going to get compiled, and you have to wait for that to happen.  Debugging performance problems with JITed code is tricky.  On the other hand, there are many solid tools for profiling C++, and mapping that back to PHP doesn't sound too hard.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's probably possible to do a little better in the static model by avoiding generating or loading code for slow paths that you feel will never actually be loaded.  I imagine right now the generated code looks like:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;code&gt;if (is_int(v)) { /* large int code */ }&lt;br /&gt;else { /* massive general code */ }&lt;/code&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The else branch is probably never taken.  It seems like there should be techniques to either move the else branch far away from the hot code so that the cold code is never even faulted in from disk, or you could try to rig up a fallback to the interpreter, which I think would be much harder and not help your code size.  On the other hand, branch predictors these days are good and moving code around is really only going to show up as instruction cache effects, which are pretty hard to predict.  HipHop probably already uses __builtin_expect to tell g++ which branch it expects to take as well.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One other major thing I remembered during the talk is that optimizing PHP turns out to be much easier than Python.  In Python, functions, classes, and globals in a module all share the same namespace.  In PHP, variables start with a $ and functions do not.  Short of eval, which HipHop forbids, there is no way to shadow a function without the compiler noticing that it might be shadowed.  For example, this works:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;code&gt;if ($a) {&lt;br /&gt;  function foo() { return true; }&lt;br /&gt;} else {&lt;br /&gt;  function foo() { return false; }&lt;br /&gt;}&lt;/code&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But this is not possible:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;code&gt;function foo() { return true; }&lt;br /&gt;function bar() { return false; }&lt;br /&gt;foo = bar;  // No can do.&lt;/code&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can also invoke functions through variables, like so:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;code&gt;function foo() { return true; }&lt;br /&gt;$func = "foo";&lt;br /&gt;$bar = $func();&lt;/code&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That will set $bar to true.  This is generally rare and easy to implement by falling back to the standard dynamic lookup.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;OTOH, local variable lookup seems like it sucks because PHP supports something like the following:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;code&gt;$which = $a ? "foo" : "bar";&lt;br /&gt;$$which = "string";&lt;/code&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Which will assign "string" to the variable $foo or $bar depending on whether $a is true or false.  Hopefully the scoping rules restrict this to the scope of the local function, so you only need to worry about this feature in functions that use it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Another feature of HipHop is that, once you forbid certain features like eval or using include to pull in generated or uploaded source from the filesystem, you get to do whole program compilation.  One of the major optimization blockers even for C and C++ is that the compiler only gets to see a translation unit (TU) at a time.  HipHop gets to see the whole program, so it makes it possible to do the checks I mentioned earlier, like making sure that there is only a single definition of a function 'foo' at top-level which is always present, so you can statically bind it at the call-site even if it's in a different source file.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Finally, Haiping mentioned that the massive binary produced is deployed to servers using BitTorrent, which surprisingly seems like exactly the right tool for the job.  You could try to be more clever if you knew something about the network topology, or you could just use a general, well-tested tool and call it a day.  =D&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;P.S. Hopefully none of this is considered FB confidential and Haiping doesn't get in any trouble.  It was a very interesting talk!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-4721579343798009065?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/4721579343798009065/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/04/hiphop-talk.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/4721579343798009065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/4721579343798009065'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/04/hiphop-talk.html' title='HipHop talk'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-8295993128178140863</id><published>2011-03-30T18:32:00.000-07:00</published><updated>2011-03-30T18:40:34.163-07:00</updated><title type='text'>Building spec benchmarks with newer libstdc++</title><content type='html'>This post is mostly for the sake of others searching with the same problem.&lt;br /&gt;&lt;br /&gt;I was having issues building the 483.xalancbmk benchmark with a not-so-new libstdc++ (4.3.4).  There's a particular source file (FormatterToHTML.cpp) that uses memset without including string.h or cstring.  This was the error message:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;FormatterToHTML.cpp:139: error: 'memset' was not declared in this scope&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;It depended on an include of 'vector' to pull in 'cstring' as a transitive dep.  That seems to have changed.  Since the SPEC benchmarks go so far as to checksum the benchmark sources before letting you run them, it was easier to add '#include &amp;lt;cstring&amp;gt;' to /usr/include/c++/4.3.4/vector than fix the application.  =(&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-8295993128178140863?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/8295993128178140863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/03/building-spec-benchmarks-with-newer.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8295993128178140863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8295993128178140863'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/03/building-spec-benchmarks-with-newer.html' title='Building spec benchmarks with newer libstdc++'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-6346999945622561027</id><published>2011-03-26T10:57:00.000-07:00</published><updated>2011-03-29T12:18:19.384-07:00</updated><title type='text'>Unladen Swallow Retrospective</title><content type='html'>&lt;p&gt;&lt;i&gt;I wrote this while I was at PyCon, but I kept revising it.  Anyway, here goes.  :)&lt;/i&gt;&lt;/p&gt;&lt;p&gt;As is apparent by now, no one has really been doing any work directly on Unladen Swallow or in porting it to py3k.  Why not?&lt;/p&gt; &lt;div class="section" id="lack-of-sponsor-interest"&gt; &lt;h2&gt;Lack of Sponsor Interest&lt;/h2&gt; &lt;p&gt;The primary reason is that we weren't able to generate enough internal customers at Google.  There are a few reasons for that:&lt;/p&gt; &lt;ol class="arabic simple"&gt; &lt;li&gt;Most Python code at Google isn't performance critical.  It's used mainly for tools and prototyping, and most user-facing applications are written in Java and C++.&lt;/li&gt; &lt;li&gt;For those internal customers who &lt;em&gt;were&lt;/em&gt; interested, deployment was too difficult: being a replacement was not enough.  Any new Python implementation had to be turn-key.  We thought building on CPython instead of starting fresh would sidestep this problem because C extensions and SWIG code would just work.  However, simply changing from the previous version of CPython to a 2.6-based Python was too difficult.&lt;/li&gt; &lt;li&gt;Our potential customers eventually found other ways of solving their performance problems that they felt more comfortable deploying.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After my internship was over, I tried to make Unladen the focus of my Master's thesis at MIT, but my advisor felt that the gains so far were insufficient to have big impact and the techniques I wanted to implement are all no longer considered novel.  Most feedback techniques were implemented for Smalltalk by Urs Hölzle and tracing for Java by Andreas Gal.  Not to say there aren't novel techniques to be discovered, but I didn't have any ideas at the time.&lt;/p&gt; &lt;/div&gt; &lt;div class="section" id="lack-of-personal-interest"&gt; &lt;h2&gt;Lack of Personal Interest&lt;/h2&gt; &lt;p&gt;Most of this was decided around Q1 of 2010.  We still could have chosen to pursue it in our spare time, but at that point things looked a little different.&lt;/p&gt; &lt;p&gt;First of all, it's a lot less fun to work on a project by yourself than with other people, especially if it's unclear if you'll even have users.&lt;/p&gt; &lt;p&gt;Secondly, a large part of the motiviation for the project was that we felt like PyPy would never try to support CPython C extension modules or SWIG wrapped code.  We were very surprised to see PyPy take steps in that direction.  That somewhat obviated the need to build a bolt-on JIT for CPython.  Also, when the project was launched, PyPy didn't have x86_64 support, but in the meantime they have added it.&lt;/p&gt; &lt;p&gt;Finally, the signals we were getting from python-dev were not good.  There was an assumption that if Unladen Swallow were landed in py3k, Google would be there to maintain it, which was no longer the case.  If the merge were to have gone through, it is likely that it would have been disabled by default and ripped out a year later after bitrot.  Only a few developers seemed excited about the new JIT.  We never finished the merge, but our hope was that if we had, we could entice CPython developers do hack on the JIT.&lt;/p&gt; &lt;p&gt;So, with all that said for why none of us are working on Unladen anymore, what have we learned?&lt;/p&gt; &lt;/div&gt; &lt;div class="section" id="lessons-about-llvm"&gt; &lt;h2&gt;Lessons About LLVM&lt;/h2&gt; &lt;p&gt;First of all, we explored a lot of pros and cons of using LLVM for the JIT code generator.  The initial choice to use LLVM was made because at the time none of us had significant experience with x86 assmebly, and we really wanted to support x86 and x86_64 and potentially ARM down the road.  There were also some investigations of beefing up psyco, which I beleive were frusturated by the need for a good understanding of x86.&lt;/p&gt; &lt;p&gt;Unfortunately, LLVM in its current state is really designed as a static compiler optimizer and back end.  LLVM code generation and optimization is good but expensive.  The optimizations are all designed to work on IR generated by static C-like languages.  Most of the important optimizations for optimizing Python require high-level knowledge of how the program executed on previous iterations, and LLVM didn't help us do that.&lt;/p&gt; &lt;p&gt;An example of needing to apply high-level knowledge to code generation is optimizing the Python stack access.  LLVM will not fold loads from the Python stack across calls to external functions (ie the CPython runtime, so all the time).  We eventually wrote an alias analysis to solve this problem, but it's an example of what you have to do if you don't roll your own code generator.&lt;/p&gt; &lt;p&gt;LLVM also comes with other constraints.  For example, LLVM doesn't really support back-patching, which PyPy uses for fixing up their guard side exits. It's a fairly large dependency with high memory usage, but I would argue that based on the work Steven Noonan did for his GSOC that it could be reduced, especially considering that PyPy's memory usage had been higher.&lt;/p&gt; &lt;p&gt;I also spent the summer adding an interface between LLVM's JIT and gdb.  This wasn't necessary, but it was a nice tool.  I'm not sure what the state of debugging PyPy is, but we may be able to take some of the lessons from that experience and apply it to PyPy.&lt;/p&gt; &lt;/div&gt; &lt;div class="section" id="take-aways"&gt; &lt;h2&gt;Take Aways&lt;/h2&gt; &lt;p&gt;Personally, before working on this project, I had taken a compiler class and OS class, but this experience really brought together a lot of systems programming skills for me.  I'm now quite experienced using gdb, having hacked on it and run it under itself.  I also know a lot more about x86, compiler optimization techniques, and JIT tricks, which I'm using extensively in my Master's thesis work.&lt;/p&gt; &lt;p&gt;I'm also proud of our macro-benchmark suite of real world Python applications which lives on and PyPy uses it for speed.pypy.org.  In all the performance work I've done before and after Unladen, I have to say that our macro benchmark suite was the most useful.  Every performance change was easy to check with a before and after text snippet.&lt;/p&gt; &lt;p&gt;We also did a fair amount of good work contributing to LLVM, which other LLVM JIT projects, such as Parrot and Rubinius, can benefit from.  For example, there used to be a 16 MB limitation on JITed code size, which I helped to fix.  LLVM's JIT also has a gdb interface.  Jeff also did a lot of work towards being able to inline C runtime functions into the JITed code, as well as fixing memory leaks and adding the TypeBuilder template for building C types in LLVM.&lt;/p&gt; &lt;p&gt;So, while I wish there were more resources and the project could live on, it was a great experience for me, and we did make some material contributions to LLVM and the benchmark suite that live on.&lt;/p&gt; &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-6346999945622561027?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/6346999945622561027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/03/unladen-swallow-retrospective.html#comment-form' title='20 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/6346999945622561027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/6346999945622561027'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/03/unladen-swallow-retrospective.html' title='Unladen Swallow Retrospective'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-1301505634121380689</id><published>2011-03-26T09:32:00.000-07:00</published><updated>2011-03-26T09:40:20.471-07:00</updated><title type='text'>Working remotely over a high-latency connection</title><content type='html'>&lt;div&gt;Does anyone have any tips for working remotely with high network latency?  My project is Linux/Windows only, so I work remotely on a beastly machine at CSAIL.  It works great when I'm at CSAIL, but terribly from Palo Alto.  My two solutions thus far have been:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Use vi over ssh.  This sucks because vi then occupies my terminal, and all my keystrokes are buffered over ssh so typing is slow.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Use MacVim and OpenAFS (or any other network filesystem here, including sshfs).  This works better for me, but when switching files it takes *forever* (&gt; 15 seconds) to tab-complete filenames or open or save a file.  I have to believe this is due to inefficiencies in AFS, because I can scp files faster than MacVim can write to AFS.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I think the ideal solution might look something like Dropbox, where I have a full copy of my source code on both machines, and I only wait for the network when I want to go run the tests.  However, I'm doing the initial sync now, and it's taking forever, so I'm not sure this is a real solution.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I haven't tried using &lt;a href="http://sshmenu.sourceforge.net/articles/bcvi/"&gt;bcvi&lt;/a&gt; or vim's &lt;a href="http://hackingthevalley.com/2010/06/02/vim-scp-how-to-edit-files-on-remote-servers/"&gt;scp:// file protocol&lt;/a&gt;.  I'm not enthusiastic about them because I tend to use vim like people use emacs, meaning I keep lots of buffers open and open new files from the same vim instance.  I want to be able to use tab completion to open new files, and I don't think vim's scp protocol will let me do that, and if it does, it would suffer from the same latency issues.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ideas?&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-1301505634121380689?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/1301505634121380689/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/03/working-remotely-over-high-latency.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1301505634121380689'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1301505634121380689'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/03/working-remotely-over-high-latency.html' title='Working remotely over a high-latency connection'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-247520415969605657</id><published>2011-03-17T13:04:00.001-07:00</published><updated>2011-03-17T13:37:34.442-07:00</updated><title type='text'>x86 register save performance</title><content type='html'>Most people who deal with machine code already know that out-of-order CISC chips like most x86 variants are extremely complicated.  Optimizing for them has always been a bit of a black art.  In my work on DynamoRIO, I'm trying to optimize instrumentation calls to straight C functions.  If it's a leaf function, we can try to inline it.  For now, I'm just switching stacks and saving used registers inline.  There are two ways you can do this.  Most C compilers, when they have to spill locals to the stack, will subtract the size of the frame from the stack pointer on entry and address the locals relative to the SP.  So, for register saves, I could do the same.  However, I could also use the builtin push and pop opcodes.  To decide, I did a microbenchmark, and the push and pop opcodes won.  Let's look at the assembly:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;/* Compile with -DPUSH to get pushes/pops&lt;br /&gt;* and without for movs. */&lt;br /&gt;.globl main&lt;br /&gt;main:&lt;br /&gt;push %rbp&lt;br /&gt;mov %rsp, %rbp&lt;br /&gt;mov $500000000, %rax&lt;br /&gt;.Lloop:&lt;br /&gt;#ifdef PUSH&lt;br /&gt;  push %rax&lt;br /&gt;  push %rbx&lt;br /&gt;  /* etc... */&lt;br /&gt;  push %r15&lt;br /&gt;  /* Mid */&lt;br /&gt;  pop %r15&lt;br /&gt;  /* etc... */&lt;br /&gt;  pop %rbx&lt;br /&gt;  pop %rax&lt;br /&gt;#else&lt;br /&gt;  lea -120(%rsp), %rsp /* Update rsp. */&lt;br /&gt;  mov %rax, 0x70(%rsp)&lt;br /&gt;  mov %rbx, 0x68(%rsp)&lt;br /&gt;  /* etc... */&lt;br /&gt;  mov %r15, 0x00(%rsp)&lt;br /&gt;  /* Mid */&lt;br /&gt;  mov 0x00(%rsp), %r15&lt;br /&gt;  /* etc... */&lt;br /&gt;  mov 0x68(%rsp), %rbx&lt;br /&gt;  mov 0x70(%rsp), %rax&lt;br /&gt;  lea 120(%rsp), %rsp /* Update rsp. */&lt;br /&gt;#endif&lt;br /&gt;  dec %rax&lt;br /&gt;  test %rax, %rax&lt;br /&gt;  jnz .Lloop&lt;br /&gt;leave&lt;br /&gt;ret&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;So, we have a loop here that saves all registers (I only need to save some for my purposes, this is just a micro-benchmark) in the two ways described.  Here are the results for running both on a Core i7 I have access to:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Command:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" &gt;gcc microbench_push_mov.S -DPUSH -o microbench_push_mov &amp;amp;&amp;amp; time ./microbench_push_mov&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;User time:&lt;/div&gt;&lt;div&gt;trial 1    0m3.400s&lt;/div&gt;&lt;div&gt;trial 2    0m3.404s&lt;/div&gt;&lt;div&gt;trial 3    0m3.404s&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Command:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" &gt;gcc microbench_push_mov.S -o microbench_push_mov &amp;amp;&amp;amp; time ./microbench_push_mov&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;User time:&lt;/div&gt;&lt;div&gt;trial 1    0m3.444s&lt;/div&gt;&lt;div&gt;trial 2    0m3.444s&lt;/div&gt;&lt;div&gt;trial 3    0m3.444s&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, the push/pop version is 1% faster, which depending on how you look at it is unintuitive.  At first, one would think that push and pop have to do more work by updating the rsp register for each instruction.  Looking at the encoding, I can say that the code size of the moves is much larger, because an offset needs to be encoded for every instruction.  "push %rax" is just 50, while "mov %rax, 0x70(%rsp)" is 48 89 44 24 70.  I suppose doing the indirect addressing every cycle is also work, but it doesn't need to be managed as a write dependency like the stack pointer updates.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With that in mind, it now makes better sense why most compilers generate pushes for saving caller saved registers on entry, but use stack offset addressing for locals.  The encoding for pushes and pops is much smaller, and the execution is marginally faster.  Locals are accessed unpredictably, so push and pop aren't really an option.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-247520415969605657?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/247520415969605657/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2011/03/x86-register-save-performance.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/247520415969605657'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/247520415969605657'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2011/03/x86-register-save-performance.html' title='x86 register save performance'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-1152500581286628103</id><published>2010-04-15T12:59:00.000-07:00</published><updated>2010-04-15T18:34:17.843-07:00</updated><title type='text'>Dazed and confused in the age of new media</title><content type='html'>We've all been talking for a long time about how the old media establishment of a few astronomically popular bands will crumble and give way to a plethora of smaller acts.  That's all well and good, and you can see evidence of it in the declining record sales and the rise of music blogs like discodust.blogspot.com and discoworkout.com.  The trend is more and more people are spending more time listening to music and searching for talented artists and sharing them with their friends.&lt;br /&gt;&lt;br /&gt;However, my experience is that I discover an artist I like, and I'm unable to purchase their music through the large channels I'm familiar with.  Let's take Kill the Noise, for example, who is a DJ somewhere in California who happens to have made a remix I like.  I go to his site, and it's all about booking information for getting him to come and perform for you.  He has several of his tracks in a Flash music player at the top of the page.  I'm listening to it as I write this post, and it's good.  There are even links to a handful of downloads, some for free and others for pay.  But I can't find a single link to an album for purchase.&lt;br /&gt;&lt;br /&gt;It makes sense: the album isn't a format that fits the model of new media.  Why should an artist release 8 or so tracks all at once in a seemingly arbitrary collection?  Why not publish as soon as each track is finished to keep the buzz alive, rather than waiting two years for new music?  However, here I am with my wallet, trying to figure out how to purchase roughly $10 worth of music without spending too much time looking for it.  I guess there's no place for this in the new media regime.  In order to find all the new hip stuff, you've got to have boatloads of time to comb the music blogs and download hit singles.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-1152500581286628103?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/1152500581286628103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2010/04/dazed-and-confused-in-age-of-new-media.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1152500581286628103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1152500581286628103'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2010/04/dazed-and-confused-in-age-of-new-media.html' title='Dazed and confused in the age of new media'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-6401331478132488528</id><published>2010-04-06T14:43:00.000-07:00</published><updated>2010-04-06T15:10:48.125-07:00</updated><title type='text'>How to configure smartmontools on an Ubuntu system</title><content type='html'>So I'm having some hard drive troubles, and I'm not sure what the cause is.  I've had two incidents so far, each of which is precipitated by my music hanging and the Amarok UI freezing and then unfreezing up to 30 seconds later.  The first time was quite catastrophic, and I had to restore from my previous night's backup.  However, I took the opportunity to do a complete system reinstall, which blew away my cron backup script configuration.  So unfortunately, this time around, I didn't have a backup to restore from.&lt;br /&gt;&lt;br /&gt;When I noticed the symptoms, I immediately made an incremental backup and rebooted to run fsck.  Predictably, fsck reported a few nasty filesystem errors, but it mostly had to do with some unmodified files checked into version control and some system fonts.&lt;br /&gt;&lt;br /&gt;Anyway, this experience prompted me to finally setup smartmontools so I can keep track of drive errors and identify if the failure is a controller problem, a connection problem, or a drive problem.&lt;br /&gt;&lt;br /&gt;There are many ways to configure smartmontools/smartd, and you can read about them in the man page and across the Internet.  These instructions are, in my opinion, the ones that require the least modifications to what Ubuntu sets up for you by default.&lt;br /&gt;&lt;br /&gt;So first, do the usual &lt;span style="font-family:courier new;"&gt;sudo apt-get install smartmontools&lt;/span&gt; .&lt;br /&gt;&lt;br /&gt;This installs postfix, which I configured as an Internet Site, and left with most of the defaults filled in.&lt;br /&gt;&lt;br /&gt;The package comes with an init script that lives at &lt;span style="font-family:courier new;"&gt;/etc/init.d/smartmontools&lt;/span&gt; and is set to run at the defaults runlevel when installed.  However, if you try running it manually via &lt;span style="font-family:courier new;"&gt;sudo /etc/init.d/smartmontools&lt;/span&gt; , you will notice that it prints nothing and fails to start a smartd background process.  It turns out that you need to edit &lt;span style="font-family:courier new;"&gt;/etc/default/smartmontools&lt;/span&gt; and uncomment the lines they have there, as shown below:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;# Defaults for smartmontools initscript (/etc/init.d/smartmontools)&lt;br /&gt;# This is a POSIX shell fragment&lt;br /&gt;&lt;br /&gt;# List of devices you want to explicitly enable S.M.A.R.T. for&lt;br /&gt;# Not needed (and not recommended) if the device is monitored by smartd&lt;br /&gt;#enable_smart="/dev/hda /dev/hdb"&lt;br /&gt;&lt;br /&gt;# uncomment to start smartd on system startup&lt;br /&gt;start_smartd=yes&lt;br /&gt;&lt;br /&gt;# uncomment to pass additional options to smartd on startup&lt;br /&gt;smartd_opts="--interval=1800"&lt;/pre&gt;&lt;br /&gt;Especially without &lt;span style="font-family:courier new;"&gt;start_smartd=yes&lt;/span&gt;, nothing will work!&lt;br /&gt;&lt;br /&gt;Now, before we're off to the races, we want to go check out /etc/smartd.conf, where most of the real configuration we care about happens.  Here is the relevant section of my configuration, with comments about each option.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;# DEVICESCAN: Scan for ATA and SCSI devices.  All lines after this one are&lt;br /&gt;#   ignored.&lt;br /&gt;# -o on: Enable online data collection.&lt;br /&gt;# -S on: Enable automatic attribute autosave.&lt;br /&gt;# -s (S/../.././02|L/../../6/03): Do a short self-test every night between&lt;br /&gt;#   2-3am and a long self-test every Sunday between 3-4am.&lt;br /&gt;# -H: If the device says it's not healthy, send mail.  This occurs if any&lt;br /&gt;#   prefail attributs are past their thresholds.&lt;br /&gt;# -l selftest: Send mail if, since the last check, a self test has found&lt;br /&gt;#   additional errors.&lt;br /&gt;# -l error: Send mail if the error log has new errors.&lt;br /&gt;# -f: Send mail if the disk has "failed", ie it's total usage is above the&lt;br /&gt;#   threshold set by the manufacturer.  Indication of age, not check failures.&lt;br /&gt;# -m: Address to which to send email.&lt;br /&gt;# -M...: Script to run to send mail and do other things.&lt;br /&gt;&lt;br /&gt;# This line is broken up for posting, but I would remove it from the actual&lt;br /&gt;# config file.&lt;br /&gt;DEVICESCAN -o on -S on -s (S/../.././02|L/../../6/03) -H -l selftest -l error -f \&lt;br /&gt;  -m reid.kleckner@gmail.com -M exec /usr/share/smartmontools/smartd-runner&lt;/pre&gt;&lt;br /&gt;The idea behind this configuration is to be a little bit less verbose than the default of &lt;span style="font-family:courier new;"&gt;-a&lt;/span&gt;, which monitors changes in various parameters, but still generate an email when things go wrong.  The results of the self-tests are stored on the hard drive, so if you're having trouble with a drive later, you can check out its bill of health and test record with &lt;span style="font-family:courier new;"&gt;smartctl -a&lt;/span&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-6401331478132488528?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/6401331478132488528/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2010/04/how-to-configure-smartmontools-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/6401331478132488528'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/6401331478132488528'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2010/04/how-to-configure-smartmontools-on.html' title='How to configure smartmontools on an Ubuntu system'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-1603939659175908194</id><published>2010-03-17T16:58:00.000-07:00</published><updated>2010-03-17T17:13:19.735-07:00</updated><title type='text'>Portable Native Client</title><content type='html'>Hey, this is cool:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://blog.chromium.org/2010/03/native-client-and-web-portability.html"&gt;http://blog.chromium.org/2010/03/native-client-and-web-portability.html&lt;/a&gt;&lt;br /&gt;&lt;a href="http://nativeclient.googlecode.com/svn/data/site/pnacl.pdf"&gt;http://nativeclient.googlecode.com/svn/data/site/pnacl.pdf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This is the first I've seen it disclosed publicly, but the summary is that Google plans to make Native Client (NaCl) portable with some kind of verifier on LLVM bitcode.  Now, if you're familiar with LLVM, then you may have heard the claim that LLVM bitcode from a C compiler is not platform neutral.  The argument is that you always have things like #ifdefs that check for the target architecture and that most C compilers (maybe including Clang? I'm not sure) lower things like sizeof(int) in the frontend.  Therefore, they claim, it is essentially impossible to compile C and C++ down to a portable representation.&lt;br /&gt;&lt;br /&gt;However, it seems doable for NaCl, because you're not dealing with arbitrary programs.  In their writeup, they explicitly say you should eliminate things like architectural #ifdefs.  They also state up front that sizeof(int) == sizeof(long) == sizeof(void*) == 4.  I'm not sure how that fits in with x86_64.  They also state that the data model assumes that integers are little endian, which isn't exactly portable either.&lt;br /&gt;&lt;br /&gt;Still, it's a cool project.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-1603939659175908194?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/1603939659175908194/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2010/03/portable-native-client.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1603939659175908194'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1603939659175908194'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2010/03/portable-native-client.html' title='Portable Native Client'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-2582967757198657771</id><published>2009-03-11T12:09:00.001-07:00</published><updated>2009-03-13T19:25:16.670-07:00</updated><title type='text'>"Blocks," or "lambdas in a non-sexp language"</title><content type='html'>Last week there was a lot of discussion around tav's proposal to &lt;a href="http://tav.espians.com/ruby-style-blocks-in-python.html"&gt;add Ruby blocks to Python&lt;/a&gt;.  Eventually, the proposal &lt;a href="http://mail.python.org/pipermail/python-ideas/2009-March/003244.html"&gt;went to python-ideas&lt;/a&gt;, and got ground into the dust by all of the objections.  The idea has been &lt;a href="http://mail.python.org/pipermail/python-ideas/2008-November/002340.html"&gt;proposed&lt;/a&gt; &lt;a href="http://mail.python.org/pipermail/python-list/2004-April/257277.html"&gt;before&lt;/a&gt;, and has always met with strong resistance.  The argument against anonymous pseudo-expression functions relies on the idea that Python already has powerful syntax for doing 90% of the things that Ruby and Scheme use blocks and lambdas for, and Guido seems to prefer it that way.  In the end, I think Guido's right about blocks in Python, but in another language with different scoping rules, blocks might be a great idea.&lt;br /&gt;&lt;br /&gt;Consider the &lt;a href="http://c2.com/cgi/wiki?BlocksInRuby"&gt;Ruby blocks examples on the c2 wiki&lt;/a&gt;.  The first three examples, which are iteration, callback registration, and resource management, all have distinct syntaxes in Python, while in Ruby they all use blocks:&lt;br /&gt;&lt;br /&gt;Iteration:&lt;br /&gt;&lt;pre&gt;# Ruby:&lt;br /&gt;collection.each do |element|&lt;br /&gt;   ...&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Python:&lt;br /&gt;for x in collection:&lt;br /&gt;   ...&lt;br /&gt;&lt;br /&gt;# Ruby:&lt;br /&gt;numbers = [1,2,3,4]&lt;br /&gt;squares = numbers.map {|n| n*n }&lt;br /&gt;&lt;br /&gt;# Python:&lt;br /&gt;numbers = [1, 2, 3, 4]&lt;br /&gt;squares = [n * n for n in numbers]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Callback registration:&lt;br /&gt;&lt;pre&gt;# Ruby:&lt;br /&gt;button.on_click do |event|&lt;br /&gt;   ...callback code...&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Python:&lt;br /&gt;# (decorators are more general, but this is a common use case&lt;br /&gt;# exemplified by Django filters and tags.)&lt;br /&gt;@button.on_click&lt;br /&gt;def raise_dialog(event):&lt;br /&gt;   ...callback code...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Resource management:&lt;br /&gt;&lt;pre&gt;# Ruby:&lt;br /&gt;File.open(filename) do |file|&lt;br /&gt;   ...read from file...&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Python:&lt;br /&gt;with open(filename) as file:&lt;br /&gt;   ...read from file...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The consensus on the python-ideas list was that the dedicated for-loop and context-manager syntax is more readable, because no matter what object you're iterating, you have a big fat keyword on the line start telling you how the next code block is going to be executed, instead of one syntax stretching to try and cover multiple unrelated use cases.  The verdict could also be interpreted as another instance of the "there should be only one way to do it" philosophy of Python.  Currently, def is the only way to create a function that can contain statements, and decorators cover many of the higher-order function use cases.  Introducing another syntax for those tasks goes against the grain.&lt;br /&gt;&lt;br /&gt;So if blocks aren't good for Python, where do they work?&lt;br /&gt;&lt;br /&gt;First of all, I think blocks in Ruby are kind of broken.  I've seen many people talk about the elegance of the Ruby block syntax, and I just don't buy it.  Why all the puncuation and magic ampersand-arguments?  What the hell is up with optional parentheses on function calls?  That's friggin' crazy when you're working with function values.  It's almost as bad as Common Lisp having separate namespaces for functions and values.  The scoping rules are also crazy.  Because there's no variable declarations, you can modify names in enclosing scopes by accident.  Python deals with this via the new 'nonlocal' statement.  Also, the whole DSL craze and the role of blocks in that is just kind of strange to me.  So forget that stuff.  What I like about Ruby blocks is that they are an innovative way to do non-neutered lambdas in statement-oriented languages without dangling parenthesis.&lt;br /&gt;&lt;br /&gt;Blocks occupy this weird middle zone between functional programming and stateful languages, because in functional languages or Lisps statements are either not allowed or are parentheses-wrapped expressions that you can stick anywhere you want anyway.  Blocks are especially relevant in whitespace sensitive languages like Python and Ruby, where jamming a statement into an expression is awkward grammatically.  &lt;a href="http://wiki.reia-lang.org/wiki/Reia_Programming_Language"&gt;Reia&lt;/a&gt; is a &lt;a href="http://unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html"&gt;good case study&lt;/a&gt; for what happens if you try to force statements into expressions.  So blocks are a little innovation to move the statements out of the expression and into a following block of code.  In Lisp, the trailing parenthesis would be no big deal, but in statement-oriented languages it really messes up your grammar.&lt;br /&gt;&lt;br /&gt;So what's the point of this stupid syntax hack so you can write multi-statement lambdas in stateful langauges?  I think the reason that new Ruby programmers are so much in awe of blocks is because they haven't been properly exposed to first class functions before.  A lot of them are web developers, and aren't interested in those high falutin' ideas about functional programming.  I think that the key to teaching someone functional programming is lambda.  Without the ability to embed executable code into an expression and pass it off to another function, you're left gesticulating wildly about how functions are values like numbers, strings, and lists.  Lambda can really demonstrate that, as they say in 6.001, "the value of a lambda is a procedure."  Once you've internalized that idea, you're ready for higher-order functions and the rest.&lt;br /&gt;&lt;br /&gt;So while I think that in the existing Python ecosystem it makes sense to not have blocks, it makes it harder to teach and use higher-order functions.  There's something to be said for the Ruby way of doing all of those examples above.  They all use the same mechanism, and that's another kind of "there should be one obvious way to do it" in action.&lt;br /&gt;&lt;br /&gt;In conclusion, if you're designing a new non-functional or whitespace sensitive language and you like the power of lambdas, blocks are probably a good way to express them.  Patching them into Python now, however, would probably take away from the simplicity of the language.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-2582967757198657771?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/2582967757198657771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/03/blocks-or-lambdas-in-non-sexp-language.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/2582967757198657771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/2582967757198657771'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/03/blocks-or-lambdas-in-non-sexp-language.html' title='&quot;Blocks,&quot; or &quot;lambdas in a non-sexp language&quot;'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-4133697141186789335</id><published>2009-03-08T20:58:00.000-07:00</published><updated>2009-03-08T21:19:12.191-07:00</updated><title type='text'>Automatic __repr__ and __eq__ for Data Structure Classes</title><content type='html'>For the compilers class project that I'm working on, I recently wrote a couple of classes that do &lt;a href="http://pastie.org/411400"&gt;simple structural equality and automatic __repr__ generation&lt;/a&gt;.  We have a lot of simple data structure IR classes in our project, and they all need __eq__ and __repr__ for testing and debugging.  Structural equality is easy; all you have to do is introspect on __dict__ and see that the attributes match recursively.  Automatic __repr__ is more difficult, though, because to produce valid Python source, you have to know the order of the arguments to the instance's __init__ method.  Fortunately, with the &lt;a href="http://docs.python.org/library/inspect.html"&gt;inspect module&lt;/a&gt;, you can call inspect.getargspec(self.__init__) and get that information.  We use the simple convention for our IR nodes that the arguments to __init__ all become attributes of the same name, so you can then use the argument names and getattr to generate the reprs of the subnodes.  Good times!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-4133697141186789335?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/4133697141186789335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/03/automatic-repr-and-eq-for-data.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/4133697141186789335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/4133697141186789335'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/03/automatic-repr-and-eq-for-data.html' title='Automatic __repr__ and __eq__ for Data Structure Classes'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-8866369621363483073</id><published>2009-02-20T10:35:00.000-08:00</published><updated>2009-02-20T13:19:03.268-08:00</updated><title type='text'>Alternatives to django-media-bundler</title><content type='html'>Apparently I didn't do a good enough job Googling for other projects when I wrote django-media-bundler.  If you Google for &lt;a href="http://www.google.com/search?num=20&amp;amp;hl=en&amp;amp;q=django+concatenate+javascript"&gt;django concatenate javsacript&lt;/a&gt; or &lt;a href="http://www.google.com/search?num=20&amp;amp;hl=en&amp;amp;q=django+minify+javascript&amp;amp;btnG=Search"&gt;django minify javascript&lt;/a&gt; there are a number of other projects that do similar things, each with different tradeoffs:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://github.com/zvoase/django-mediacat/tree/master"&gt;django-mediacat&lt;/a&gt;:  To use this tool, you descibe your JS packages in Django models using the django-admin interface.  You then configure a URL to point at the mediacat.views.cat view function, and pass it the package names you want as GET arguments.  mediacat then caches the resulting file in the database model, and sends the appropriate ETags and Last-Modified tags to make the browser cache the request contents.  Last updated: 2008-11-02&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/django-compress/"&gt;django-compress&lt;/a&gt;:  This tool can be configured to automatically regenerate its bundles by checking the file modification times of the original source files.  Obviously, if you're using a content distribution network or a cookieless static domain, this feature might not work out of the box, but if you're a small-time operation, this is nice.  It also allows versioning the filenames of the bundles so that when you have new bundles they will bust the browser cache.  You can also use the YUI compression tools if available, and finally django-compress has a templatetag that will source the compressed script if compression is enabled, or the individual source files if disabled.  Finally, I'd like to point out that this project looks the most mature, as it was started on 2008-04-28, and it has a well-written wiki and not just a README.  Last updated: 2008-12-05&lt;/li&gt;&lt;li&gt;&lt;a href="http://github.com/paltman/django-compact/tree/master"&gt;django-compact&lt;/a&gt;:  Looks like it's not quite finished yet, but so far it looks like its main feature is the templatetags that will link to individual script sources or the bundle.  Last updated: 2009-01-30&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://launchpad.net/django-assets"&gt;django-assets&lt;/a&gt;:  Has Jinja 2 templatetags, and supports automatic regeneration of bundles based on file mtime.  Bundles are defined inline in the templates instead of in a central configuration.  This seems less good to me, because you want to keep the number of different bundles small, so that the user only has to download one script bundle.  Sometimes you want more than one because a particular page has a lot of JS, but usually you want all JS to be cached after the first page load.  Last updated: 2009-02-08&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/django-assetpackager/"&gt;django-assetpackager&lt;/a&gt;:  This tool supports cache busting by putting the bundle generation timestamp in the bundle filename.  It also has a templatetag that will source individual scripts in debug mode and just the bundle in production mode.  Last updated: 2008-06-21&lt;/li&gt;&lt;li&gt;Finally, &lt;a href="http://github.com/rnk/django-media-bundler/tree/master"&gt;django-media-bundler&lt;/a&gt;:  While somewhat unrelated to concatenating and minifying JavaScript and CSS, my project supports image spriting, which is a pain to do by hand.  It also employs an interesting little heuristic 2-D bin packing algorithm to try and arrange the images into a square-ish rectangle of minimal area.  It has templatetags like a couple of the others, but it doesn't have cache busting.  That's an important feature I'd like to add.  The auto-regeneration I'm not convinced about, because it breaks down when you're not using a simple single-server setup.  Also, it means that your templatetag has to do a bunch of file system calls while its rendering the template, which isn't a terrible idea, but it feels less than perfect.  Last updated: 2009-02-15&lt;/li&gt;&lt;/ul&gt;Having browsed the source trees of each of these projects, in my (biased, of course) opinion the best tools here are django-compress and django-media-bundler.  django-compress is mature and has the most JS &amp;amp; CSS bundling features, while the media-bundler is simpler (which can be good) and has image spriting.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-8866369621363483073?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/8866369621363483073/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/02/alternatives-to-django-media-bundler.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8866369621363483073'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/8866369621363483073'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/02/alternatives-to-django-media-bundler.html' title='Alternatives to django-media-bundler'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-2995500912297111153</id><published>2009-02-14T21:14:00.000-08:00</published><updated>2009-02-14T21:56:06.180-08:00</updated><title type='text'>django-media-bundler now supports sprites!</title><content type='html'>Over the last week I've been working on adding &lt;a href="http://www.alistapart.com/articles/sprites/"&gt;image spriting&lt;/a&gt; support to &lt;a href="http://github.com/rnk/django-media-bundler/tree/master"&gt;django-media-bundler&lt;/a&gt;.  For the uninitiated, image spriting is a technique that Google, Yahoo, and other fast web sites use to speed up page load times.  What these web sites do is to combine all of their small icon images into one medium size image, and then use CSS background image offsets to display each icon individually from the master.  For small icon graphics, the overhead of the HTTP requests dwarfs the size of the actual image, so this speeds things up drastically.&lt;br /&gt;&lt;br /&gt;With the help of the &lt;a href="http://www.pythonware.com/products/pil/"&gt;Python Imaging Library&lt;/a&gt;, I was able to read the images, measure their dimensions, and paste them together into the master image.  However, given icons of arbitrary size, it's not clear what is the best way to lay out the master image.  Having just taken an advanced algorithms course, this problem seemed like a variation of the bin-packing problem.  The bin-packing problem is NP-hard, but once you have a name for something, it's a lot easier to Google up some &lt;a href="http://www.csc.liv.ac.uk/%7Eepa/surveyhtml.html"&gt;simple heuristic algorithms&lt;/a&gt; to solve the problem.&lt;a href="http://www.devx.com/dotnet/Article/36005/"&gt; &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Finally, I had to figure out how to get the sprites into the page.  I found that in audio-enclave we use images in all sorts of interesting ways that make it difficult to abstract away the spriting behind a template tag.  In the end I generated a set of CSS rules with the background image and offsets and decided to let the user figure out how to display the images.  Working in the sprites was, for our project, more pain than it was worth, and I had to break a couple of nice CSS abstractions to force a DIV node into an element which had none before.&lt;br /&gt;&lt;br /&gt;Anyway, implementation details aside, now audio-enclave has excellent front-end performance!  Check out these Firebug net tab screenshots:&lt;br /&gt;&lt;br /&gt;Before spriting:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_GitFwvbR9ak/SZet9bvF9MI/AAAAAAAAAA8/wlCXebDztz0/s1600-h/latency_bundled.png"&gt;&lt;img src="http://4.bp.blogspot.com/_GitFwvbR9ak/SZet9bvF9MI/AAAAAAAAAA8/wlCXebDztz0/s400/latency_bundled.png" style="cursor: pointer; width: 400px; height: 225px;" alt="" id="BLOGGER_PHOTO_ID_5302898357180953794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;After spriting:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_GitFwvbR9ak/SZet9fybSaI/AAAAAAAAABE/7BSKqB2qhWc/s1600-h/latency_sprited.png"&gt;&lt;img src="http://2.bp.blogspot.com/_GitFwvbR9ak/SZet9fybSaI/AAAAAAAAABE/7BSKqB2qhWc/s400/latency_sprited.png" style="cursor: pointer; width: 400px; height: 141px;" alt="" id="BLOGGER_PHOTO_ID_5302898358268676514" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-2995500912297111153?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/2995500912297111153/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/02/django-media-bundler-now-supports.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/2995500912297111153'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/2995500912297111153'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/02/django-media-bundler-now-supports.html' title='django-media-bundler now supports sprites!'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_GitFwvbR9ak/SZet9bvF9MI/AAAAAAAAAA8/wlCXebDztz0/s72-c/latency_bundled.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-1503416056815493401</id><published>2009-02-01T08:08:00.000-08:00</published><updated>2009-02-01T09:18:25.244-08:00</updated><title type='text'>Why CPython Will Live On</title><content type='html'>Recently there has been a lot of interest on &lt;a href="http://www.reddit.com/r/programming/"&gt;proggit&lt;/a&gt; and &lt;a href="http://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt; in creating new language implementations on top of existing VMs like the JVM, the CLR, and the Erlang VM Beam.  The list of language implementations targeting existing VMs that I can name off the top of my head is long: Clojure, Scala, Jython, JRuby, IronRuby, IronPython, Reia, Ioke, Boo, Fan, F#, and Fortress.  I was even working on a small language side-project that had the eventually had the goal of targetting the JVM.  This should all be old news to you if you've been paying attention to PL news, and I think it's a pretty good idea.  When languages share runtimes, you end up being able to communicate between them nicely, and everyone can collaborate on writing one high-performance garbage collector and one solid JIT.&lt;br /&gt;&lt;br /&gt;However, you can only stretch this principle so far.  &lt;a href="http://wiki.reia-lang.org/wiki/Reia_Programming_Language"&gt;Reia&lt;/a&gt; is implemented on top of Beam because it wants capabilities that the JVM doesn't have built-in, like lightweight processes and good fault-tolerant message passing.  So what I want to talk about is that while I think Jython and IronPython are a worthwhile ways to get pure Python to play nice with languages on those respective VMx, I still think CPython has a very bright future.&lt;br /&gt;&lt;br /&gt;I realized while reading Guido's &lt;a href="http://python-history.blogspot.com/"&gt;History of Python&lt;/a&gt; blog that one of Python's very early design decisions was to integrate well with existing systems, meaning things written in C.  As Guido explains, this was a reaction on his part to his work with the ABC group, which wanted to hide all those scary systems problems away from the programmer and isolate them on some higher-level plane.  While this may be good for a learning language, this limits your ability to do interesting things with code that already exists.  Python solved that problem by having a relatively simple C API, especially when compared to things like JNI.  Going further, the choices to use the GIL and reference counting are decisions that clearly make the life of the C extension module writer easier.&lt;br /&gt;&lt;br /&gt;Writing C extension modules isn't exactly peaches and cream, so Greg Ewing came up with Pyrex which was forked into &lt;a href="http://www.cython.org/"&gt;Cython&lt;/a&gt;.  Cython is a "medium"-level Python-like language which gives you access to C primitives and allows you to call out into both Python and C with ease.  The &lt;a href="http://www.sagemath.org/"&gt;Sage Project&lt;/a&gt;, a project to repackage and combine Python math software, uses it extensively.&lt;br /&gt;&lt;br /&gt;To give you an idea of what this lets you do, let's say you're writing a C++ plugin for an existing crummy Windows application and you want to make your life better by embedding Python.  All you have to do is call PyInitialize() from your plugin, and then you can start interacting with Python code.  Cython makes this even easier, because you can write your DLL stubs in Cython as cdef's and you can just wrap it up as a DLL.  Doing this kind of thing is technically possible with the JVM.  However, while Cython is easy, the JNI is a pain, and the JVM has a massive startup time penalty as compared to CPython.  I'd like to link to a more in depth explanation of this technique, but the person I know who is using it hasn't written it up online yet.  If and when it does go up I'll link it.&lt;br /&gt;&lt;br /&gt;In conclusion, until the day that C's star sets, CPython will continue to be an incredibly useful tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-1503416056815493401?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/1503416056815493401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/02/why-cpython-will-live-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1503416056815493401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/1503416056815493401'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/02/why-cpython-will-live-on.html' title='Why CPython Will Live On'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-406145404840542633</id><published>2009-01-29T20:00:00.000-08:00</published><updated>2009-01-29T21:10:09.893-08:00</updated><title type='text'>Announcing django-media-bundler</title><content type='html'>&lt;span style="font-family:verdana;"&gt;Just a couple of days ago, I looked at the Net tab in Firebug while doing a full refresh of an audio-enclave page.  Here's what I saw on our (needlessly) most javascript heavy page:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_GitFwvbR9ak/SYKCStZUKQI/AAAAAAAAAAM/eCZKDKHVUj4/s1600-h/latency_no_bundle.png"&gt;&lt;img src="http://3.bp.blogspot.com/_GitFwvbR9ak/SYKCStZUKQI/AAAAAAAAAAM/eCZKDKHVUj4/s320/latency_no_bundle.png" style="cursor: pointer; width: 320px; height: 286px;" alt="" id="BLOGGER_PHOTO_ID_5296939369676155138" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;As you can see, with all those external files, we were flagrantly violating three of the Yahoo best practices: &lt;a href="http://developer.yahoo.net/blog/archives/2007/04/rule_1_make_few.html"&gt;minimize HTTP requests&lt;/a&gt;, &lt;a href="http://developer.yahoo.net/blog/archives/2007/07/high_performanc_5.html"&gt;put scripts at the bottom&lt;/a&gt;, and &lt;a href="http://developer.yahoo.net/blog/archives/2007/07/high_performanc_8.html"&gt;minify JavaScript and CSS&lt;/a&gt;.  Obviously, what we wanted was to keep developing our JavaScript just as we had in separate modules, and add a build step to our deploy to concatenate and minify our JavaScript and CSS.  We had heard of Rails' &lt;a href="http://synthesis.sbecker.net/pages/asset_packager"&gt;Asset Packager plugin&lt;/a&gt;, so we looked for a Django plugin that did basically the same thing.  We were unable to find one, so we wrote our own, dubbing it &lt;a href="http://github.com/rnk/django-media-bundler/tree/master"&gt;django-media-bundler&lt;/a&gt;, and threw it up on GitHub.  Here are the Firebug results of enabling bundling, minification, and deferred JavaScript on that page:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_GitFwvbR9ak/SYKCSs65H4I/AAAAAAAAAAU/s2CxaS_k7IM/s1600-h/latency_bundled.png"&gt;&lt;img src="http://4.bp.blogspot.com/_GitFwvbR9ak/SYKCSs65H4I/AAAAAAAAAAU/s2CxaS_k7IM/s320/latency_bundled.png" style="cursor: pointer; width: 320px; height: 180px;" alt="" id="BLOGGER_PHOTO_ID_5296939369548554114" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Using django-media-bundler&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The media-bundler is a reusable app, so to install it all you have to do is download the source and add it to INSTALLED_APPS.  Describe the JavaScript and CSS bundles you would like to create in settings.py as explained in the media_bundle.default_settings module.  By default, deferring is enabled, and bundling is disabled when settings.DEBUG is True to assist debugging.  You can override those values in your settings module.&lt;br /&gt;&lt;br /&gt;To source your scripts, instead of writing&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;script type="text/javascript" src="/url/myscript.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;link rel="stylesheet" type="text/css" href="/url/mystyle.css"/&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-family:verdana;"&gt;you put &lt;span style="font-family:courier new;"&gt;{% load bundler_tags %}&lt;/span&gt; at the top of your template and write&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;{% javascript "js_bundle_name" "myscript.js" %}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;&lt;span style="font-family:courier new;"&gt;{% css "css_bundle_name" "mystyle.css" %}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;At the bottom of your page in your base template you should put&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;{% deferred_content %}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;where-ever you want to load the scripts in production.  We recommend putting a second  section after your body.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: verdana;"&gt;And that's it!  As a future goal, we'd like to help automate the horizontal spriting of PNG icons.  We would have a &lt;span style="font-family: courier new;"&gt;{% sprite "sprite_bundle" "icon_name.png" %}&lt;/span&gt; tag that automatically generates a div with a background and offset.  Alternatively, it might be nice to run the script and CSS files through the template preprocessor to allow them to access the urlresolver so we don't have anymore hardcoded URLs or janky inline template JavaScript.  Happy hacking!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-406145404840542633?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/406145404840542633/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/01/announcing-django-media-bundler.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/406145404840542633'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/406145404840542633'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/01/announcing-django-media-bundler.html' title='Announcing django-media-bundler'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_GitFwvbR9ak/SYKCStZUKQI/AAAAAAAAAAM/eCZKDKHVUj4/s72-c/latency_no_bundle.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5346954106731174402.post-406036846324523655</id><published>2009-01-29T19:31:00.000-08:00</published><updated>2009-01-29T20:00:39.336-08:00</updated><title type='text'>Announcing Audio-Enclave</title><content type='html'>&lt;span style="font-size:100%;"&gt;&lt;span style="font-family:verdana;"&gt;This January during MIT's Independent Activities Period I've been working hard on my dormitory floor's music server software.  We operate a rack-mount server that plays music in our showers and in our lounges.  The server, dubbed "nice-rack", is a hub where all of us come together and share our tastes in music.  Using the web interface we wrote, anyone can upload and play music on the server, and anyone can dequeue anyone else's music.  Naturally, the server is a flashpoint for arguments about what constitutes good or even tolerable music.&lt;/span&gt;&lt;span style="font-family:verdana;"&gt;&lt;br /&gt;&lt;br /&gt;Recently, the entire web interface was rewritten to use Django, and the backend was rewritten using Gstreamer.  The project has reached the point in its life where we, the contributing residents of East Campus Second West, think that other people might find the code useful, so we've put the code under a BSD license and moved it to Google Code:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a style="font-family: verdana;" href="http://code.google.com/p/media-enclave/"&gt;http://code.google.com/p/media-enclave/&lt;/a&gt;&lt;span style="font-family:verdana;"&gt;&lt;br /&gt;&lt;br /&gt;Audio-enclave should be useful to anyone who wants to share a communal sound system.  Personally, I have vague notions of using audio-enclave as an input to my family's living room stereo, so that we can play music from our collection on the stereo without CDs or laptops that have to be on, open, and plugged in.&lt;/span&gt;&lt;span style="font-family:verdana;"&gt;&lt;br /&gt;&lt;br /&gt;Eventually, we intend to support some kind of remote API so that we can write little mobile phone apps to control the playback.&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5346954106731174402-406036846324523655?l=qinsb.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://qinsb.blogspot.com/feeds/406036846324523655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://qinsb.blogspot.com/2009/01/announcing-audio-enclave.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/406036846324523655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5346954106731174402/posts/default/406036846324523655'/><link rel='alternate' type='text/html' href='http://qinsb.blogspot.com/2009/01/announcing-audio-enclave.html' title='Announcing Audio-Enclave'/><author><name>Reid Kleckner</name><uri>https://profiles.google.com/108532745084733145449</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-DfjMWkaZuak/AAAAAAAAAAI/AAAAAAAAA5A/QAQ3GXRerZ8/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
