 And it's time to talk about performance. Who here thinks that Ruby is fast? Raise your hand. Come on. Like a few people think that Ruby is fast. Like everyone here is thinking it's slow, right? No way. I disagree with you guys. Ruby was slow, but it's not slow anymore. Unless you allocate a lot of memory, which pretty much every application out there does. And this is because, this is why your applications are slow. It's not because Ruby is slow. Two-one is extremely fast, but it's because you allocate memory. Why am I talking so much about memory consumption? And the reason is simple. I do believe that memory optimization is the number one thing that you should do to make your application fast. Why? As because Ruby adds a huge memory overhead, because every object allocates extra 40 bytes in memory. And this, these gets combined with a slow garbage collection algorithm. And what you get in result? High memory consumption. And what does high memory consumption mean? It means that garbage collection needs to spend more and more time. And the size of average Rails application is about 100 max, not less. And it means that garbage collection actually needs to spend a lot of time doing its work. So if you optimize your memory usage, what you get is you get less time spent in garbage collection. I've seen applications spending as much as 70%. 70% of time doing just garbage collection. That's nothing. And that's why, again, that's why you need to optimize your memory. You can get this 70% of time off. And that is why Ruby 2.1 is important. It does not optimize your memory, but it improves the performance of garbage collector itself. And the garbage collector gets about 40% faster. Here are some examples to prove my statements that memory optimization is important. This is the good old application that is still running Ruby 1.8. And it's been there for five years, and the number of requests it's serving is increasing every year, but it still runs the same hardware that we bought back in 2010. It doesn't need a new hardware. Why? We optimize memory. And here's another example, this time showing how 2.1 helps. This is the modern Rails app running on Heroku, and 2.1 Upgrade gave about 40% of improvement as I expected it to be. But what if you cannot upgrade or do not want to upgrade, or you upgrade in nothing helps to improve your performance? And the answer is simple. You just optimize memory. Let me show you a synthetic but still pretty good example of how memory optimization works and why it helps. This is the simple program that reads CSV. There's pretty large CSV, actually. It reads it line by line. It converts strings from uppercase to title case, and then outputs it. It's not memory optimized. As you can see, it needs a lot of memory to load the dataset. It needs memory to parse it. It needs memory to do string operations. It needs memory to collect the output. So what you get with different Ruby versions? 1.9 and 2.0, it's about 20-something seconds to finish. 2.1 gives you that 40% improvement, and 40% is I see it everywhere. Every application that got upgraded, every my application, of course, that got upgraded to 2.1, got 40% improvement, simply because garbage collection runs faster or less often. In this case, it will run faster. So you can actually improve performance to about 15 seconds, slightly more. But you can do even better than this. If, and only if, you optimize memory. And here's the simplest possible way to optimize memory for this application. And the number one thing I optimized is I output my CSV data line by line so that I don't need to store it in the memory. And, of course, I use the BAN operators, like Downcase BAN, GISA BAN, and Capitalized BAN, that do in-place string replacements. It's not totally optimized. It still loads the whole CSV into memory and parses it there. But it is already a great improvement. It performs even better than non-optimized program running Ruby 2.1. So remember, 2.1 makes the program run in 15 seconds. This one runs in 13 seconds. And the amazing thing is that all major Ruby versions perform the same. And the thing is that if you optimize your memory, it doesn't matter which Ruby you run. You'll probably still get some good improvement by running 2.1. But if you optimize memory, your application will perform the same in all Ruby versions. And if you cannot upgrade, this is great news for you. And even 1.8, the good old 1.8 that some people do use still can perform the same, or almost the same. So Ruby 2.1 is not a performance bullet and you should optimize memory. And now the interesting part. How? How do we optimize memory? I want to talk about five strategies to do this. And the first one is to, well, tune your garbage collector. If garbage collector is the thing that makes your application slow, you can probably change settings. What do you want to check? You want to change the settings to shift balance between number of times the GC runs and peak memory usage. The default Ruby wants you to have low peak memory usage and high number of GC calls. So if you want to reduce the number of GC calls, you need to sacrifice the peak memory usage. You need to check this. You need to measure to see how well it works for your application. But let's step back for a second and talk briefly about how GC in Ruby 2.1 in this case 2.1 works. Ruby 2.1 has two types of garbage collection. One is minor, which works on the new objects that are allocated after the previous garbage collection call and the major GC, which operates on all objects. So minor GC happens when you don't have enough space in the heap to allocate new objects or you allocate every other 16 to 32 megs of data. Major GC happens when too many objects become old or shady, but I'm not going to dive into details of this, and when they grow 16 to 128 megs. What do we see from this? How do we decrease the number of GC calls? The other thing is, of course, to increase the memory limits. If you increase the memory limit, GC will run less often. And another thing is that you can actually grow your heap space for Ruby objects a little bit faster so that Ruby will not have to run GC that often to let you allocate new objects. And there are five environment variables in 2.1 that you can use. Of course, you can set memory allocation limits for new generation, for old generation, and old generation means that these are the objects that survived at least one garbage collection so they become old and they need to be collected less often. And you can increase the growth factor. You might notice that I grayed out other variables, and my advice is to never touch them unless you really understand what happens. You can find advice on the Internet to touch any slots, heap-free slots, do not. They usually make things worse if you do not understand what's happening. But you can increase growth factor a little bit, don't increase it into 5x or something like this. Two-something would be good for you. Also, you can increase memory limits. So every time you allocate 16 megs of memory, it will run garbage collection. You might think that, okay, 16 is too low. Make it 32. Make it 64 at maximum. If you make it more, again, you will have peak memory consumption increasing. You don't really want to have your peak memory consumption to increase into gigabytes of memory. So be careful with this. There are two excellent blog posts that I want to point you to if you want to learn more about how GC works and how to tune it. I'm also working on a Ruby performance book which explains in detail what this all means and how to tune it. It's still not released, so if you go to Ruby performance book, you can simply sign up for updates. I would be happy to share our beta version of the book with you once it's done. So that was GC tuning. Now, next strategy would be to limit the growth of your Ruby processes. And the interesting thing here is that Ruby processes grow over time. Have you seen this before? Rails application is growing, growing, growing, and never shrinking back. I see it all the time. And the reason for that is simple. Ruby allocates space for new objects. If you allocate space for a mail-in object, Ruby will let you do this. But Ruby will never give this space back to the operating system. It will still be able to use this space inside the Ruby process. But once it grew, it will never shrink. That's why you need to do something to prevent your processes, especially long-running processes like web workers from growing. And I would advise you to have some internal control. For example, every time you process the request, you can check your memory usage and be sure to check RSS, not another metric. And quit if you exceed certain limit. Another would be the software layer. For example, Heroku does it for you. Actually, it will kill your application if it's grow over 500 max. If you deploy yourself, you can use monitor, gut, or whatever else monitoring solution that you like. But that's not enough. Because you can still run out of memory. If you run out of memory pretty fast, no monitoring solution will help you. You need to tell your operating system kernel to actually set the limit. The memory limit for your application. I find that set our limit extremely well works. It works very well. Unfortunately, if you deploy on Mac, not many people do this, it will not work for you. But on Linux, it works. Make sure you use it. You set our limit. Background jobs do not really need this level of control. What you need to do for background job is to fork. What fork does is that it lets your child process allocate as much memory as it wants to. And then you quit the child process and then you release all this memory back. So if you have a long running worker doing some memory heavy operations, that would be a good idea to do. Third strategy. Control manually. Now, I'm not talking about doing GC disabled. You might find advice to disable GC then do something really heavy and then enable it. Just don't do it. Especially if you deploy in Herok or any other system like that. Why? Because you will run out of memory pretty fast. It's not a good idea to disable GC. What I mean by manual control is this. It's the thing that you should use. You should force run GC between requests in your viable application. It works pretty well. And Unicorn has built in a way of doing this. And for Ruby 2.1, there is an even better way of doing this with a GC tools gem. The only thing you need to keep in mind is that you need to have more workers to compensate for the time that your workers spent doing garbage collection. It is extra work and you need to make sure that you have enough resources to serve requests while your workers doing garbage collection. So this is the stuff you need to think about. Now it's my favorite. The best Ruby code out there is the one that does not exist. Sometimes you do not want to write Ruby at all. Why? It's simple. Other tools there can solve your problems better. For example, let's take a look at this table. This is the table with employees and their departments and their salaries. And say you need to calculate a group rank inside the department by salary. And imagine that you have not 7 employees but 7,000 or 70,000 employees. What do you do? What do you do in Ruby? You load the whole data set into memory. You pay your overhead to active record, to instantiate active record objects. And then you sort. For that you also need some memory. And then you rank. And it's just not a good thing to do in Ruby. Imagine loading 70,000 objects into memory. How much memory will you use? I think gigabytes. I do see that you use gigabytes of memory just for this. How well your Ruby application will perform? It will run in 20 seconds, 30 seconds, something like this. The best way to do it is SQL. If you have a good database, like Postgres, Oracle, you can just use SQL. This thing will finish in milliseconds or seconds depending on your data set size. And Postgres has this beautiful feature called window functions, which do exactly this task and they do it well. I know there is a sentiment inside Ruby community and Rails community that SQL is to be avoided. I disagree with this. SQL is a great tool. It's been there for 40 years, and web frameworks come and go, come and go, and SQL is here to stay. So sometimes it is a good idea to use SQL instead of Ruby. And data crunching, any operations on the data, any calculations is exactly this example of doing things in SQL. So finally, learn some SQL. It's a good investment. You will use less memory because of this. You will find yourself writing your application in SQL, like I did. So what do we do if we still have to write Ruby code and we cannot write SQL? You just need to avoid memory hacks. There are a lot of memory hacks out there. A memory hack is everything that copies your data in memory. Everything that increases your memory consumption. Just a few examples. Of course, you want to use in-place string modifications. So every time you see something banned, you should be sure that you are doing a good thing. Just every time ban is good. My favorite is shift operation, which must be used instead of plus equals, just because plus equals creates a new object and copies two previous ones into this new one. And shift will make in-place modification and save your memory. When you're working with files, of course, make sure you are reading the file line by line and not the whole sale. Same for CSV parsing or any other parsing that you do. Active record is also a huge memory hack. But I'm not telling you that you should avoid active record. Not at all. Do use active record. But sometimes it's okay to not use it. For example, update all will actually execute just a single SQL query. It will not instantiate active record object. That's the good thing here if you just want to update one attribute and do some proper quoting to avoid security problems. That's a good thing to do. Just update all and that's it. Sometimes you want to read result into memory and do something with it. And if this result is big enough, and I would say if this result is 1,000 objects or more, just iterate without creating active record objects. So select and iterate. That would save you up to four times of memory. Another thing that I recently discovered and still do not quite understand is that JSON serializer takes too much memory. If someone here knows what's happening and why it's happening, I would be happy to learn from you. But what I know is that the example above and example below are not the same thing. Example above usually takes twice as much memory. It somehow copies your JSON one more time than necessary. And in some cases, I've even seen the memory leak out of this. So if you're using serializers, make sure you're not having what I have in my Rails 4 application. I have a memory leak because of this. Again, this is a new thing and I don't know why, at least until now. We have some time to talk about tools. So those were strategies. And which tools do you use for memory optimization? Of course, you use GCSTAT, which is a great tool, which will tell you how often you call garbage collection. What is the memory usage? What are your memory allocation limits? And judging from these numbers, you can predict when the next garbage collection will happen. So this one is useful. Another thing that's less known is called object space.so. You might know object space module, which has this count objects function. But there is another extension of this, which has some functions to check the memory size of your objects. For example, the class object in Ruby actually uses more than 40 bytes of memory. It uses 40 bytes plus 1 kilobyte, more than 1 kilobyte. Object space also has functions to trace reachable objects from your object. So if you're unsure what you reference and what you allocate, just use reachable objects from. And it also has some tracing built in. If you want to learn more, here are some links for you. And I will tweet link to this presentation so you can find it online later. At some point before, you could use RubyProf to profile your memory to see what exactly allocates the memory and how much. Not anymore, unfortunately. I did the support for memory profiling in 1.8. Somebody else did for 1.9. But there is still need for somebody to do the same thing for 2.1. And Ruby2.1 actually has all everything built in in Ruby. It's just a matter of doing support in RubyProf. So what do you use instead of RubyProf? I would recommend to use Walgreen. Who here knows what Walgreen is? A few people. So for those of you who don't know what Walgreen is, it emulates your computer. It emulates your CPU. It's not a full blown emulator like VMware or whatever. But it emulates your CPU, and it can trace what your application does. And there are some tools inside Walgreen, and one of the tools is called Massive, which is heap profiler. It traces object allocations, and it traces how much memory you allocate. To use it, you just run Walgreen tool Massive and your Ruby code. It will do something. It will print the output. To see what happened, you need some visualizer. Walgreen has a massprint visualizer. It has nothing to do with Microsoft. It's just called a massprint. But I would recommend to use some GUI tool for that, which is called Massive Visualizer, which looks like this. So once you run your program, you can see how much memory you allocated at any point at the time. For example, here you can see the memory allocation peak. I allocated 10 megabytes more. And in some cases, you can understand what allocated this memory. For example, here you can probably see the string constructor being called, which is stringU, or whatever it's called. So yes, I did allocate 10 megs of memory in one string, like in this example. This X variable is 10 megs. So that's a pretty good tool to understand how much memory you use over time, and if there is a memory leak, you should be able to figure out where it happened. All right, so that's about it. Thank you for listening, and any questions?