Performance Improvements Using Judy Arrays
github.com
github.com
A pet peeve of mine is how people can do all the CS necessary to get nice data structures for their in-RAM data, but seem to forget everything they know and use very bad structures when they spill to flash or disk storage.
That sounds basically exactly like our course so far. I'm only in the first year though, so I guess it's a bit early to comment on course coverage. But yep, all RAM data structures so far.
One useful data structure is the B+ tree http://en.wikipedia.org/wiki/B%2B_tree
http://en.wikipedia.org/wiki/Log-structured_merge-tree
There're a bunch of fascinating performance tradeoffs between B+ trees and LSM trees, which come out when you're choosing between something like MySQL InnoDB or PostGres (B+ tree) vs. LevelDB or Apache Cassandra (LSM trees). LSM trees tend to be faster for inserts and for write-heavy applications, and they offer very fast reads and writes for frequently-accessed data. They also depend upon the bandwidth of the disk rather than the seek time, and bandwidth has of late been increasing significantly faster than seek times. OTOH, they offer very variable latency, as an insert might trigger a major compaction, while B+ trees have a bounded latency limit. A lot of the distributed systems work at Google (a big user of LSM trees via BigTable) comes from the need to work around the poor 99th percentile latencies of LSM trees.
Interestingly enough you can consider hash tables and Tries to be O(log n) since in order to have N distinct keys, the maximum key length must be at least log(N).
A web app involved in a site as complex as Github's should really contain only the parts required to service web pages. A service like language classification is clearly better designed as a standalone service.
Aside from working around GC overhead, compositing big app from many smaller apps have many advantages. You force yourself to encapsulate complexity with strictly focused, network-friendly APIs (we use REST ourselves), which changes the way you have to think about programs. Hiding the implementation behind an API also allows you to swap out the implementation without the client ever knowing. Since the client and server are separated, you have fine-grained control over how much resources you are willing to spend on which components. And so on.
Having done monolithic Rails apps for many years, it's a model I am not going to back. Today, our web apps contain only UI logic. No database, no models, no data logic, just UI all the way. Everything else is handled by specialized components.
Replace the token matcher with a simple classifier (a maxent will work very well here) with n-gram of characters features through a hash-kernel and you get a very accurate, fast and low memory system.
I've build one two-years ago who accurately classified a bit over 100 different languages with only 64k features in the end. (So requiring only 8*64ko of memory) And this was without using file extensions as they weren't available in our case.
Before any hard optimizations, first check the methods used, and next the algorithm, anything else should go after.
1) Is there an easy tutorial somewhere on calling out to native code from Ruby?
2) Could the team at Github possible give a little more detail on what you did to get all of those pretty benchmarking graphs? How did you get all of the info on memory usage and CPU activity (I assume it wasn't just time {command} > file.txt)
The canonical documentation is here: https://github.com/ruby/ruby/blob/trunk/README.EXT
* Avoiding Ruby language features
* Rewriting it in a different language
This is why I lean towards static languages like Go, Scala, and Java.
1. Build a site in ruby (insert langauge/framework the creators are most comfortable/familiar with and can iterate quickly). 2. Build a community of a certain size that causes it scaling problems. 3. Identify the hotspots and re-implement them in more performant way.
This seems entirely reasonable to me. In fact, I'm guessing this is the path that all applications that get to Github scale take. Of course, it's easy to be snarky and identify Ruby as the problem. (Building the community is the hard part!)
I think another bit that you've missed here is that once you have the users and are looking at scale, you've learned a lot about what you actually need. This fits well with all of the Lean Startup stuff: startups are an experiment.
In other words, before you're actually at scale, how do you know what your requirements are? Better to build things in something that lets you discover these requirements as quickly as possible, then fix an inconsistencies in what you've built, rather than try to imagine what your future requirements may be, build that, and turn out to be wrong.
To use one of the more famous examples of a pivot, if the Flickr team had spent a ton of time analyzing their anticipated needs for a game engine, that would have all been wasted when they pivoted to photo sharing.
On the smaller scale, if you asked me today why rendering GitHub's views is slow, I probably would have guessed that it's their SOA-style architecture, an overhead between components. I then could have wasted tons of time optimizing that, with little impact on what actually mattered.
But you can't take these measurements until you have a system that actually exists in the material world.
The % of C code was less than 3%, from what I remember, and C is significantly more verbose than smalltalk, too.
I hear that happened a lot with java as well, using JNI sections for performance purposes (this was before hotspot compiler got decent).
This post is about Github moving some code from ruby to C for performance, MRI's GC, and judy trie-like data structure and how it compares wrt to both performance and memory consuption with hash implementations.
It's a reference to the Beatles' song, "Hey Jude" (which opens with "Hey, Jude, don't make it bad | Take a sad song and make it better ...").[1]
[1] http://en.wikipedia.org/wiki/Hey_Jude
Edited for clarity.
Personal preference though.
The graphs never show a 30K token mark, which is what the heap-allocation counter showed for their [programming language] classifier.
It's not clear to me that much RSS was saved. Maybe 20Mb? So, the next question is how many classifiers are running, where such a "hit" actually matters. Again, there is no 30K mark on the runtime graphs, but let's assume the generation to the graphs' left are linear. It looks like we're saving a half-second and removing most of the jitter, but it's not made clear how the removal of GC chunking has any effect on processing outside of the classifier. I just can't imagine how a couple of these classifiers can't keep up with the push rate of files to Github -- the classification improvements are neither low-hanging or part of the BigO in Github's stack. The process seems very able to run asynchronous at push time and un-deferred, if unrun, on page view. Unless they're seeing more than 1 million pageviews/sec (per classifier; just run more? one 20Mb service per httpd server?), because I can't really imagine hitting more than 10x their tokens (300K tokens), which is still only ~50Mb; the RSS graph, again, has a weird scale to suggest they might grow their tokens by 67x.
the issue is with the patents - has anyone been contacted yet about the usage?
2. unconvinced that they can't do better w/ the right hash table (which will be thousands of lines fewer of code). but since they're using a library, i'll give them a pass. not even a benchmark vs. a quick hash table version makes all the claims about "judy arrays are better for our purpose" suspect.
The software from HP is under the LGPLv2.1 which says "Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license."
That can imply that there are no additional patent protections should you use the LGPL'ed version of Judy or its derivatives. YMMV. Consult your nearest patent attorney for details.
The patent expires in 7.5 years.
I wouldn't have used the prefix approach, instead storing a token once in the judy array, and using the data stored to indicate which languages match the token.
This just feels like "we have this huge and slow Ruby behemoth here, but using magic Judy arrays we made this little gear turn insanely fast!".
I think what Github has done is exactly what you're supposed to do: build your product on a platform that allows for quick growth and easy iteration, identify the slow parts, then swap those out for faster "gears" as you said.
Edit: I find it rather humorous that you find algorithmic research to be a magic bullet, but believe switching platforms to be a viable solution when faced with a bottleneck. I believe you have that backwards :)
My point is that the CS is never a sideshow. Improvements gained from changes in algorithmic complexity vastly outpace improvements gained from switching languages/platforms.
Worry about your algorithm first and your platform second.