 Video equipment rental costs paid for by peep code screencasts. I want to talk about some different libraries in Ruby that are kind of under-appreciated and I want to show you what kind of things we can make them do for us. So that's what this is about. I'm back at Lone Star, which I'm really excited about. Those who don't know me, I'm James Edward Gray II. I created the Ruby quiz originally and ran it for three years, though it's run by Matthew Moss now, so I don't do that anymore, but I started it. I've released a few open source libraries, including the faster CSV that was mentioned earlier, and I've written a couple of pragmatic programmer books that have quite a bit of Ruby in them. I've talked at every Lone Star Ruby conference so far. How cool is that? You guys really don't realize what a miracle that is. I came down here last year and talked for one hour on the TV show Heroes, and they invited me back this year, so that was pretty cool. We can't talk about Heroes this time because, well, Season 2 sucked, right? So, can't do that. So after we go like this, what do you think? Yeah, let's do that. We might also talk about some of this, but more Battlestar Galactica, of course. For those who don't know Battlestar Galactica, the new show, is there anybody who hasn't seen Battlestar Galactica? Raise your hand if you've not seen. Oh my God, wow. Okay, you guys got homework. In fact, you may have to miss the next talk. Okay, so in Battlestar Galactica, the humans created the Cylons. The Cylons got tired of the humans, and they kind of separated. And then at the beginning of the new series, the Cylons come back to wipe out the human race. This show has to be the most depressing show on TV. It's terrible. It starts at the end of the world and goes downhill from there. Okay, so why am I talking about Battlestar Galactica at the Ruby Conference? The Battlestar Galactica, the ship that these people are on, was to be decommissioned. It was their ancient junk, basically. And it's now the only thing keeping the humans alive. It's their last holdout. And those of you who know me know that I love those kind of odds. I love the underdogs. Those are my kind of things. But what does that have to do with Ruby? You may have actually heard this rumor. It's going around. Has anybody heard that? It really might be slow. Yeah, I don't know. I've heard a couple of people say that. This is my opinion of that rumor. Yeah, it's total crap. Some of you may remember the slides actually right out of my talk from last year. I thought that was cool. Anyways, Ruby goes as fast as we want it to, okay? Because we're in charge of that. Some people say you can speed up Ruby by switching to C, but you can actually use C from inside Ruby, so I guess we don't even need to switch. But you don't really need that very often. You can use other libraries. Some of them are written in C, so that's like using C without having to do C. And if they're specific to your problem, they can speed things up. We can always add more processing power, right? That's a big trend these days. Computers are cheap. These are easy to come by. You can always do that. The big win, of course, in my opinion, is always adding better data structures, restructuring the problem to better fit what you're trying to do. So we're going to talk mostly about this last one, but through the eyes of the other two, using libraries and processing power. I'll show you some ways to speed Ruby up. So some tools you can use to do that are N-Array, SQLite, RBtree, FSDB, Renda. And then the most important tool for all of us is always thinking outside the box, right? That's the number one skill you've got to have as a programmer. So let's talk about N-Array. We'll start there. That's numerical array is what the N stands for, and it's really good for numbers crunching. The salons have a huge advantage over the humans. They have, not girls, it's numbers. It's numbers. We'll get to the girls later, okay? Most of the human race was wiped out in the initial attack, and salons don't die. If they die, they just download to another body so they just come back. So they have these great number advantages. So sometimes you just got to crunch some numbers. Everybody knows that. Ruby's numbers were built for ease of use, which is nice, and we appreciate that and love using them. But unfortunately, that makes them a bit slower because they have to do things like automatically convert between FixNom and BigNom or coerce different types and things like that. So there's more to do, and they're a bit slower. Something like C's numbers were built for speed, right? They can only go so big, and they move really fast and stuff like that. We can actually borrow C's numbers inside Ruby using NRAE. That's what it's for, to make a whole bunch of C's numbers and do really fast number crunching. So to give an example problem, I have this PPM image library that's very simple. If you don't know the PPM image format, it's the one you can learn in like 30 seconds, which is the reason I use it. It's just a big grid of numbers, basically, and you can generate images from it. And this little wrapper library I have, it's under 100 lines of code. I noticed it was taking about 1.3 seconds to generate a 400 by 200 pixel image. So I said, I want to see if I can speed that up. I switched from using a two-dimensional array of color objects. That was how I was defining the image data to a three-dimensional NRAE, and use that instead. I had to change like 10 lines of code. It wasn't hard at all. I'm going to show you all the changes I made. And the same image now creates in about a hundredth of a second. So that's a pretty big speed difference. And it's not hard to do. So how do we change it? This is the initial code and the line in particular that we're interested in is the very last line, creating a two-dimensional array by width and height. And then I stored color objects in those positions of the array and then converted them when they got written out. If we change it to use NRAE instead, notice that we have to add two requires. We're requiring RubyGems and requiring NRAE. NRAE has been made into a gem, by the way. So you can install it normally with just gem install NRAE. Don't do that right now. You can do that. And then the last line changes to this. NRAE has several constructors depending on the size and type of numbers you want. In this case, I just need bytes for an image. And this is a three-dimensional NRAE. So the first dimension is the width, second one is the height. And then the third one is just three pixels wide or three bytes wide to cover RGB values of color. So the other change we had to make was marking pixels. Initially, I would mark pixels like this. The array was in row major. So you use the Y and then the X and then just assign the color to that spot. This line changed very little in the NRAE version. It now looks like this. The difference is that I'm slicing into my NRAE and I use all the dimensions together. And I grab three of the bytes instead of just one and then replace them all with an array. You can do array assignment. So change all three of these numbers to the values of this color. And the only other change that you have to make to do something like this is in the drawing code. Originally, I would iterate over the rows in my image, stringify the content and print them out. And the new code is pretty much the same. The difference is that with NRAE, iterating is almost always best done by slicing your array in the way that I hope you the most. In this case, I do kind of a three-dimensional slice. Get the entire width, just one version of the Y, so one row, and then all three colors. I have to transpose it because when I do that slice, it comes back as a two-dimensional wave because we took two out of the equation and it comes back colors by width, but I want it width by color. So I use NRAE's transpose and it lets you arbitrarily reorder the dimensions however you want. And then the printing it out is basically the same. NRAE can convert itself to just a byte string, so that's really all you need here. NRAE has a lot of other nice features. You saw me using it with bytes. It can work with other size integers, two byte integers, four byte integers, floats. It even does complex numbers. I showed a little bit of indexing and iteration. It does data generation arithmetic comparisons, bitwise manipulations, and statistics. So there's really quite a bit of number crunching you can do with it. It has a pretty large API, and it's online so you can go see it. On RubyForge, there's also examples there seeing play around with them. But actually the easiest way to learn NRAE if you want my trick is just open IRB and make a tiny NRAE 3x3 or something and then start doing things to it. Like add one to it and watch what happens. Try to add two NRAEs together and watch what happens. You get the hang of it real quick just playing with it. I'm going to show one more NRAE example. This one's actually out of the examples that are online for NRAE. There's an implementation of Conway's Game of Life using just NRAE. It does imaging and everything. I've very much simplified it here. I'm just going to show you one step. But this is how you can do the Game of Life with NRAE. This code right here does what I mentioned before, the data generation. I've made a 5x5 area here just so it's small and fits on a slide well. And then I take the center three pixels by slicing into that array and generate random ones and zeros to stick in there. So you can see that ends up looking like this. And I get some cells in the center. I'm not using the outer edges. Those are just in there to make my math easier. So the center three by three is actually where the game would be played. This code right here does all the counting you need for the Game of Life. And it does it by continually slicing the NRAE over and over again to get the cell to your upper left and then the cell straight above you, then the cell to your upper right, etc. until you have all the neighbors and you just add all those together and you end up with these counts right here. And you can see all the neighbor counts are in the proper spot and tell you how many neighbors there is. And then even cooler, this is the actual implementation of one step of the Game of Life using NRAE. When you do comparisons with NRAE or bitwise operations or arithmetic or whatever, it does those operations across the entire array. So you just have to compare if there's three neighbors then there's going to be a guy there no matter what. If he was there, he survives. If not, one's born. If there was two neighbors and a guy there before then you get a new one. And that works like this. It changes the array to this. So this is a lot of work done with just some simple slicing and arithmetic and you can get just ridiculous speeds out of it. So it's very worth it. Another way to get great speed out of Ruby is to use SQL lite. This is a really underloved library. I think some of us use it in Rails sometimes but we have a tendency to use it behind an ORM which is kind of a shame because it's kind of cool if you get it out of that and let me show you what I mean. So thinking about that is pretty hard, right? This is Gaius Baltar. He betrayed the human race in the Sylon attack. So I can't believe you guys haven't seen battle stuff. Now I have to explain all this stuff. So anyways, he does all this time thinking and trying to stay one step ahead of the humans and that's a full-time job. SQL lite has already solved a lot of problems about data storage, right? So it's kind of like you're already one step ahead of the problems if you use SQL lite. And it gives you an entire language to express your needs for your data or play around with your data. So that ends up being very important. And the reason I love to use it is when I don't know what I'm going to need because I can start off describing my data in one way and putting it in tables and then when I realize something else is going to happen I've got to do something different. It's very easy for me to move that data around change it, change the language I'm using at an index, whatever. So let's show a real example of this. This was an old Ruby quiz, the IP to country problem. The idea was that given some IP address we want to return the country that IP is registered to. There's an online database for this. It's just a giant CSV file. But we wanted to, this being a quiz we wanted to kind of make it tricky and see if we could do it efficiently with memory and speed and things like that. And even though it was a quiz this is actually a task I pulled from my job. We do some site analysis on visitors to a website and this is one of the things we do. A lot of blogs use this trick now too, right? And show different images depending on where you're coming from. So the data looks kind of like this. It's just, and this is just a few lines of it the real file is about six megabytes. And it gives you ranges. The beginning IP address to the ending IP address. This may not look like it, but that's an IP address right there. They're encoded as a four byte integer, right? So each part of the IP is then part of the integer. And that's it. For solutions for this problem a lot of people sub the Ruby quiz with a binary search on the file. And that was fast obviously. Binary search is fast, right? So many of them pre-process the file to make that easier. If you just do a normal binary search you're going to land in the middle of a line most times. So then you've got to figure out how to get to the next line and make sure you're not accidentally sometimes skipping over one line. To make that all easier, a lot of them pre-read the file and took the beginning IP, the end IP and the two character country code. You could pack that into two four byte integers and then the two bytes for the country code. Each line was 10 bytes and then you could use simple math to jump right to a line. That's because they're a lot smarter than I am. So I couldn't think all that out. So I tried it with SQLite just to see how it would do. And I got to say I was pretty surprised. I thought, wow, I wonder how hard it would be to make SQLite go as fast as those binary solutions went. The answer was not hard at all. The very first thing I tried was pretty much the exact same speed, about one third of a second to find a country for an IP address. I didn't have to be clever. I didn't even have to put an index on any of the columns, which I was really surprised by. So just very simple straightforward SQL. And it was easier for me to use the full country names. Those who pre-processed the file, they went with the country code because they knew it would be a certain size. But I could use the whole name. I was sticking it in some SQL table. So I did. So the setup code looks like this. Nothing interesting here. I don't think a bunch of requires for different libraries I'm going to use. The important one is the last one, SQLite 3. There's another driver for SQLite 2, but 3 has a bunch of cool features, including Unicode support and stuff. So I recommend going with 3. Here's the part where we actually build the database. This part up here creates the database. All you have to pass in is the name of the file, right? SQLite is an entire database and one file. So you pass the name of the file and that's your database. And then I'm just executing a statement. This is what executing a statement looks like in SQL. If you don't really care about the return value, I'm just executing a statement to create a 3 field table, right? Low IP, high IP, and the country name. This code does the loading. The part above where I have highlighted is just using the open URI library and GZIP Reader so I can read the CSV file directly from the Internet Zipped and don't even have to say it locally. I use faster CSV to parse out the CSV data and pull the pieces I'm interested in and then you can see my insert right here. I'm inserting entries into the table. This shows a couple of new things. You can see I use positional values. Those question marks in my SQL statements are placeholders. And then I pass the arguments for those placeholders at the end of the execute call. That's loading the whole database. And then this is the part that actually does the query. Again, at about 1, 1, 3rd of a second. So the query here, SQLite, has a helper called getFirstValue. So if you're just going to do a query for one particular number or field name or something, you can call this getFirstValue and it returns just the first thing in the first row in the query. And this time I use some parameters again but I use name parameters. SQLite can do that too. So I use my name parameter and then I pass in a hash for those values and that's how they come back. There's a lot of things people don't know about SQLite and that's really a shame. For example, SQLite is totally free and I don't mean like free as in free speech or free as in beer, I mean free as in freedom. Anybody who writes code for SQLite signs an affidavit relinquishing copyright and putting their code in the public domain. And those are all kept in a fire safe at the main developer's office. So SQLite is free. You can do whatever you want with it. You can receive query results, especially through the Ruby driver as a hash, so you can work with it by name instead of by index position. You can also have it convert SQLite types to Ruby types. So if it's an integer in SQLite you get it back as an integer or a fixed nom in Ruby. It can work with in-memory databases and this is crazy fast. I'll show an example of this in a second. So you can just build an in-memory database and then still use all the advantages of SQL to mess with your data. This is a cool feature that's under Lib 2. You can run queries across tables in multiple database files in SQLite. So you can bring two database files together and then do queries based on two separate databases. And you can define SQL functions in pure Ruby code. If you want to add some capabilities to your SQL you can use just fly out Ruby to do that. So here's what some Ruby friendly data looks like. You can see that I've asked for the results to be returned as a hash and turn on type translation so I can get my SQL types and normal Ruby stuff. Then when I execute my query down here you'll see in the match line that I'm actually referring to people's by name now instead of by position. And also when I pass them into pack I count on the fact that they'll already be integers and they are. This is another example of a SQLite query too. I stuck a block on the end of it and that's how you iterate over the matches line by line. End memory, I redid the whole thing as an end memory database to see if I could get it to load a lot faster. It did load a lot faster. The initial conversion took over seven minutes originally and this version of the end memory database it loads in 50 seconds. And then of course queries are just faster than I blink so fast enough. The only change was instead of specifying the file I want to stick it in you specify this special colon memory colon and that's where it comes from. Then the other cool thing here is we're using prepared queries. You can set up a query and you have your placeholders and then you can fill those placeholders in at the time where you run the query. So the query's already been parsed and prepared and everything and it executes very fast. Two more cool SQLite features are attach and functions. Here in attach we assume I have some user database and I go ahead and just open that and then I attach that country IP database I built for the IP to country example and then this lets me do a query to look up if I have a user's IP address and that user's database I can look up which country they're in using my totally other database. So that's a really cool feature and I didn't want to figure out how to convert an IP to an integer and SQL because that sounded painful. So I did it in Ruby instead and defined a new SQL function in pure Ruby called IP to int. I did the Ruby code to do the conversion and then I can use it in my SQL statements and it works just fine. So you can change SQL to whatever you need it to be using pure Ruby code. And then right here this is another helper I'm using. I fetched the very first row of the results and that is the answer to my query. Lots of cool things to know about SQLite. It's very fun to play with. But I kind of sidestep the problem, right? Why do people get it with a binary search? What if we really wanted to do it with a binary search? If you want to do that, I recommend using RBTree which is a binary tree. And even though the silence have evolved into these more human forms they keep around these like metal guys for combat and heavy lifting, right? Because sometimes you just need big guns. Binary search is definitely a big gun of computing. We use it when we need things to be very fast, right? RBTree is a red-black tree. So if you're familiar with an AVL tree it's even more efficient than that by a little bit. And RBTree is written in C. So it's got everything going for it, right? It's super efficient. So we'll do the IP to country thing one more time. We'll use a real binary search. But the good news is we don't have to write it or be clever. We'll just use RBTree. This drops the search time below one one thousandth of a second. Less than one one thousandth of a second. Think about that. This is Ruby, right? And we're getting answers in less than one one thousandth of a second. So next time anybody tells you Ruby is slow you have my permission to give them strange looks. We can marshal RBTree. It works just like other Ruby objects and we can spin it out to a disk. That's how we'll get our persistent database. And we'll use some bound searching methods to perform the search. So I'll show you what that looks like. My setup is totally unchanged except for the require line. Now I'm requiring RBTree instead of SQLite. The load gets a lot easier because RBTree works basically like a hash. So this is the line right here when I'm loading the database. I have to be a little clever here because we're talking about ranges. And RBTree really just keys on a single key. So I key it by the low end of the range and then I just store the high end of the range along with the country. And later I can check to make sure it's below that high end. I'll show you how I do that. And then this is what I mentioned before. You can marshal it out just like you can marshal anything else in Ruby. So spin it to a disk and you have your tree on your disk persisting. And then the search itself, again pretty much the same. This is reading the RBTree back end. So just a marshal read line there which is fine. And this is the actual search. The cool thing is RBTree has these bounds method. It's upper bound, lower bound and bound. And you can basically provide an upper bound or lower bound or a bound at each end. And it will return entries that are as close to or equal to that bound as it can. So I set an upper bound and find the low end of the range that's closest to the IP address that the user gave me. And then I make sure that we're still under the high end of that range. That's why the put statement's a little tricky. And then print out the country if we are unknown, if not. RBTree has some other nice features. Somebody asked about 1.9's hash ordering. If you need an ordered hash, RBTree is basically all you need. And you can control the ordering which is like what you're asking about. RBTree is also, it sorted set in the standard library is written to use RBTree if it's available. So if you just install RBTree, sorted set gets 15 times faster on standard iteration. So that's kind of neat, just extra. Here's an example of an ordered hash. This is not ordered like Ruby 1.9's ordered hashes. They're ordered by insertion order. This is ordered by key. See, the keys I put in, it doesn't matter what order I put them in. When I iterate over the hash, they come out in order. Because it's a binary tree, it goes through them in order. So if you want to change the ordering, all you have to do is change what you're using for keys, right? Whatever you order by, on the keys, that's the order. And then magically improving sorted set, I just wrote this trivial program that made a sorted set of the entire dictionary. And I just called two array on it because that would force Ruby to put them in order. And this is how I figured out that it's 15 times faster just to have RBTree. The only trick you need to do is you need to make sure you require RubyGems before you require set so that set will be able to find RBTree. Another cool library that's underloved is FSDB. That stands for File System Database. This is the first one I've shown that's not a gem. Unfortunately, you can't install it as a gem. But you'll find it on SourceForge. I'll give the URL in a sec. The readme file has the three magic lines that I'll install. It's very painless. So the reason the humans are still alive is because they have really great leaders. They're flexible and quick thinking. And that's important with your data too, right? Because your needs on your data change all the time. You have to stay flexible. Data can be in different formats. It can be related in different ways. And FSDB gives you a lot of flexibility for both of those. This is the URL of where it's found. Be careful with that URL. That's not RubyForge. That's SourceForge. I don't know what that is. Some kind of ancient software tracking system. I don't think we use that anymore. Some of you may know I work on Scout, which is a Rails and server monitoring tool. So we do a lot of software monitoring at my job. That means we collect statistics from various servers at regular intervals. And then we later analyze this, mainly in graphing. And on Scout, we let you build your graphs any way you want. You really need to go play with our graphing. I kind of see it. We let you choose what data you want to graph side by side and over what period of time. And show you how that works. It's great for analyzing trends and finding problems and stuff. And this turns out to be something relational databases don't do well at all. Time series data is just not something. Because even with a good index, it just turns out to be too much data most of the time. And so in order to solve this, you've got to think outside the box. You've got to come up with something different. You need to store the data in a way that you can focus on the part that only matters now, right? Well, FSDB is essentially a hash backed by your file system. So the keys are paths in that hash. And just by building a path, you can drill down to the part of the data you care about. It's kind of like a more granular version of a P store, right? If you're familiar with P store at all. And by building that path, you drill down past all the data that doesn't matter anyway. So you don't ever consider it. You don't search it or anything. It's just automatically thrown out. This isn't the exact technique we use behind the scenes in Scout because that's a lot more complicated and a lot less general. But this kind of trick we recently tried to improve our graphing and it was taking us about four seconds or so to generate good graphs. Now we're doing it well under a second. And you have to go play with our graphing to see how cool that is. I promise nobody does graphing better than Scout does. So it's cool to check out. So to give you an idea of what FSDB looks like, this is like a file structure that it would create, right? Let's say I have some server stats, and then I have them keyed by year, month, day, hour, and then the actual data is in minute files, right? And you can see there at the end that the forks, because I have the two different hours in there, but imagine if it forked way higher up the tree, like in the year, right? So then when you just make the first part of that path to 2008, you're tossing out all the irrelevant years of data, right? They're never considered. So it can be very efficient. So creating a database, if we wanted to store some time series data, creating a database looks almost just like it does in SQLite, except you give the name of a directory instead of the name of a file, not directories where your database will be kept. And then literally you use FSDB like you use a hash, right? The key is just a path. So here I turn the time into a path ordered by year, month, day, hour, minute, and that dot OBJ on the end of it tells it what kind of data I want, which FSDB takes dot OBJ to mean Marshall data. So it marshals it out to the disk and marshals it back in. And then for querying the data, all I have to do is construct the path that goes to the data I want, right? And then I can treat it like a normal hash, DB path, and I get back that Ruby object. Or in this case, I build paths to directories above the data, and by using those directories I get an array of all the things in that directory, which I can iterate over and recurse to sum up the data. And then this is just some samples of me using it. I put a few entries in the database and ask questions of it to average some statistics. So depending on how many values I give it down the tree is how much data gets averaged. So it's very simple to use, just like a hash. It's multi-thread, multi-process safe. We're going to talk about communication in different processes with Rende here in just a second, but FSDB is another option. You can use it for multi-processing stuff. It has transactions, both kind, shared and exclusive. And you can define your own formats for files. Let me show you a couple of these. This is what transactions look like in FSDB. If you use the browse method, you get a shared transaction. So other people can be reading while you're reading, but nobody can be writing. And inside this block is your transaction. Replace is your write transaction. You get an exclusive lock on the data, and then you return from the replace block whatever you want to replace the data with. So in this case, I've just added a field to the data. And no one can be in the content while you're replacing. Both of these are DRB safe too. So if you stick to these two methods, you can actually serve an FSDB object over DRB or Renda, which again, I'll talk about more in just a second, but that means you can actually have a shared hash backed by a file system over DRB. It's pretty cool. And custom formats. This is using FSDB to look at PNG images. I just stuck some PNG images in there, and I built my own FSDB format. This is all it takes right here. It's some red expression that matches your extensions. I told that I needed it to be in binary, and then I didn't make it a right format. I'm not going to write PNGs, but for reading them, I just find some key statistics. They're width and their height. Right? I return those. And then you can see I use this helper method. FSDB has a few of these helpers that let you iterate over items in various parts of the path. And I use this helper to show different images and their widths and heights. So I turn my image directory into a database using FSDB. One last library I'm going to talk about is Renda. Renda is dirt, simple, inter-process communication. Very easy to talk between multiple processes you have going. And this one doesn't need to be installed at all. It comes with every copy of Ruby, so you don't have to worry about installing it at all. See, I told you we'd get to the girls. The Cylons have six. She's a Cylon. She's like the ultimate in PR, right? Because even the Cylons have to network. Right, for obvious reasons, right? Networking is important because CPUs are so easy to come by, right? If you want to do more thinking than CPUs are trivial to come by, you get more processes working on the job, it goes faster. Renda makes communicating between those processes very easy. It's trivial to pass data back and forth. So just given a random problem, we want to take a scrambled word, run through the dictionary, and see what all it matches. It's not nothing hard, it just takes time. And this is just a placeholder task for any task that requires some processing, right? In this case, we need some IO and some very simple comparisons. So the trivial solution, just solving the problem, is this right here. This is the whole trick of the method. If you split whatever word you have on characters, sort those characters, and rejoin them, you gain kind of a unique signature for that word. And any word that has the same characters will have the same signature. So then we can just compare those signatures, and that's how we find matches in the database, right? The rest is just a IO loop to loop over the database. But let's say we want to make this go faster when it divides the work. Well, that means we've got to put more processes on it, right? And we're talking about true multi-processing, like forking, not like Ruby's threading model, which is not exactly multi-processing, unless one of them is blocked. The problem is, once you have multiple processes, you have to think about, well, how do they talk to each other? Well, Render comes with this thing called a tuple space, which is like a blackboard, basically, a whiteboard, I think it's what it's usually called. But somebody can put messages up on there, and then somebody else can take messages off of there. So it's a really simple way to pass messages back and forth. And this task is IO bound, so I can't make it very fast, because there's only one hard drive in this computer, and I still have to read through the entire dictionary. But even this computer has two CPUs with two cores each, right outside the common now. So I put four processes on it just to see what would happen. It had the time doing this search. So the setup's basically nothing complicated. We have to require Render tuple space. You see my signature method there. Again, the cheat of how I compare words. I have a workers variable that holds how many processes we're going to have work through this problem. Then in the actual workers themselves, the IO code looks a little tricky, but the reason is I'm cutting the dictionary into equal chunks. I figure out based on which worker I am, which part of the dictionary I'm going to be reading, where it starts and where it ends, and I seek to that part. And it's a little bit tricky because I'm probably going to land in the middle of a word, so I have to go to the next word, et cetera. But basically all I'm doing there is dividing the dictionary. Then when I figured out which words in my portion of the dictionary match our scrambled word, this is all I have to do to put it up. Assuming there's some tuple space out there, I give it the URL to the tuple space, wrap it in a proxy, and then I can write tuples to it. Tuples are just arrays, right? A list of things. I write the pattern, the original pattern, and what I descramble that to. Okay? So then to collect the results, all it takes is this. We get the tuple space, this process is the one, sorry, that actually creates the tuple space. So you make a new one and you serve it as a DRB object. And then to get the tuples out of it, we just call take. That pulls off a tuple that matches the pattern we pass to take. And pattern tuples are really simple. No matches everything. So if you put no in some slide, you get whatever that always matches. Everything else is done by triple equals, right? The triple equal operator. So you can use regular expressions as I've done here to match against the pattern. You can use a class as I've done here to say I want an array in this position. It's just matching just like you would do in a case statement. And this gives me back their answers, the scrambled list, which I can just print. Rinda has other nice features. You can set expirations for a tuple time, so you can put some message up there and say, this is only relevant for the next five minutes. If they don't take it, then it expires. Rinda also comes with a ring server, which you can use to do zero configuration networking. That's like Apple's Bonjour protocol, right? Where you can connect to things without even knowing their address and stuff like that. Here's an example of the ring server. You have some client start a ring server like this, and you can see what I'm serving up here is a tuple space. I just give it a tuple space and I say, put this on the web somewhere. Notice I didn't give it an IP address or URL or anything like that. I just put this up there. And then in the other side, when you want to get it back, you just use ring finger. Ring finger primary and get it back for you. That's what I wanted to show you about processes going faster and how you can speed Ruby up. But I'll take questions if you have any. Any questions? Yeah, what you got? What'd you say? URL for your slides? I haven't put them online yet, but I will. I'll put them up. We're working on getting a file server for the conference to put all the slides on, so hopefully we'll have that. Okay, anything else? Okay, that's it. Thanks.