RubyConf 2019 - Compacting Heaps in Ruby 2.7 by Aaron Patterson

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
>> Hello. Hello. Okay. I guess let's do this. Unfortunately, my oh. Okay. I got to shut off the Wi Fi here. Um, hello. Oh geez. Am I up here? Okay. Hello, everybody! Hello! I'm really, really excited to be here today. I'm so excited to give a 40 minute long ignite style presentation. Which was the slide I was going to put in here, but my tethering is too slow, so I'm just telling you the joke instead of putting it in slide form. Imagine that there is an ignite slide here. Okay. So, I'm wearing this hamburger hat today because I heard I needed to dress up, wear my formal attire. But I'm gonna take this off now, because I feel it's probably bad for the recording. I don't want my face to get all dark. Hello everybody. Hamburger hat! So we're going be talking about compacting GC for MRI. Hello. Hello! I apologize. I know this is the third day. We're nearing the end of the third day and you have come to an extremely technical presentation. So, my apologies. My name is Aaron Paterson. My given name is @tenderlove. I changed my name to Aaron. You can call me either one of those. This is what I look like on the internet. I have two cats. This one's name is Gorbachev Puff Puff Thunder Horse. He is the most famous of my cats. I have stickers of him. If you would like a sticker. This is Sea Tac Facebook >> CAPTIONER: Don't wait for me... I got Gorbachev! >> I am extremely nervous in front of you all today. But actually, I stayed at a Holiday Inn Express. Here is me. I'm on the Rails core team. We're the team responsible for developing Rails, and I looked up my commits, and my first commit was here in 2009, so I have been a commiter my first commit was about ten years ago, but I have been on the core team since 2011. I'm also on the Ruby core team which is responsible for developing Ruby. And my very first commit to Ruby core was ten years ago in October. This is my ten year anniversary on the Ruby core team. [ Applause ] Thank you! Now, this is not to say that I know what I am talking about. That is absolutely not true. I need you to take your expectations for this presentation and lower them a bit for me. And when they're down here, bring them down just a bit more. I'd really appreciate that. The way that I got on to the Ruby core team is I was able to crash Ruby a whole lot, and I got a lot of seg faults and those produced core files, and they were Ruby core files. Ahhhh! All of you may actually already be on the Ruby core team. What you should do is go check and do ls /cores and you may also have many Ruby core files as well. So, today I'm going to be talking about compacting GC for MRI, but first I want to talk about controversial features in Ruby 2.7. Matz talked a little about these in his presentation and I want to talk about them too. The first one is the pipeline operator. It got deleted, but I want to talk about it anyway. It looks like this. And I know that there was a lot of controversy around this. People did not like this operator implemented in this way. But I thought to myself that Ruby is designed for developer happiness, as Matz says. I would change it to a smile operator. You can see the pull request. It's up there. Unfortunately, it got rejected and I think it's because I guess it's because the pipeline operator was canceled. So, that's unfortunate. We could have had emojis in there, but we do not. The next thing I want to talk about is Typed Ruby. How many of you are excited about getting types into Ruby? Yeah! All right. That's great. That's great. But the thing I don't understand about it is I thought we already typed Ruby... I know some of you may copy and paste from stack overflow, but we typically type things so... Let's talk about let's actually get down to what we're supposed to be talking about today, which is manual heap compaction for MRI. This is a patch that I wrote for Ruby, and it's in Ruby now. It took me about three years to complete this work. And I have to admit, this is the most difficult project I have ever worked on in my life. This is the most difficult thing I've ever written in my life. And I usually don't say that I have any new ideas. I'll give presentations and I'll say I don't think of anything new, but I think I finally had a new idea, and that's this one. I will show you how I know this. Actually, Chris Seaton tweeted this. He is on the truffle team. He said that I had a novel solution for not moving objects referenced from C. Now Chris is he comes from an academic background, so I want to translate this slide a little bit or this tweet a little bit for you. So, when Chris says novel solution, what that actually means is that I am a genius. [ Laughter ] So thank you so much. So, I want to, before we get into the meat of this, I do want to say a quick couple of thank yous. I want to call out a couple of people specifically to thank. That is Koichi Sasada and Allison McMillan. I'll tell you why. When I first came up with this project, for many years I kept thinking oh, it's impossible to get compaction implemented in MRI and I just thought that was a thing that couldn't be done. And I explained this to Koichi and he said to me, why not? Why is it impossible? And I said to him I don't know, just many smart people tell me that and he said maybe you should try it and see if it actually is. And I was like okay. So, then I tried it, and it is possible. But unfortunately, it took me a very long time to implement this. And it was, as I said, this was the most difficult project I've ever worked on. So, I would get stalled and depressed about it. And Allison reached out to me and said hey, I would like to work with you on this project. So, every week I would pair with her on this. And that meant that I don't particularly like pairing with people when it's just me staring at some really not great C code. Trying to figure out what's going on. So, each week I would try to figure out what we were gonna do, plan it, and actually do that pairing session. So, she helped me move this project along, and it wouldn't be here today without these two people. So today's topics. I am going to talk about Ruby's heap, the compaction algorithm, implementation details, results, and if I have time at the end, I will talk about debugging techniques. I think a lot of people talk about GC algorithms and implementations, but I don't think they talk about debugging garbage collectors and I want to talk about that if we have time. So what is compaction? It is taking allocated memory and free memory and rearranging them. Imagine we have memory that looks like this with allocated interspersed. And we can combine the blocks to look something like this. This may maybe some of you remember this. This is defragging a hard drive. But instead of defragging persistent data, instead what we're doing is defragging data that's in memory. One question is why would you want to do this? What is the benefit of compaction? One benefit is efficient memory usage. For example we have a heap layout that looks something like this and we want to allocate a new chunk of memory. That chunk that we would like to allocate is too wide so it won't fit in this free area. So, maybe we look at this free area. And it won't fit there either. It won't fit in either of the two places so we will get an out of memory error. We can't allocate this chunk. If we compacted it, it is wide enough and we can allocate into that chunk so we're able to use this memory more efficiently. Another reason to compact is more efficient use of CPU caches. So, when a program reads memory, it has to go through the CPU to get the memory. It has to read some memory and in order to do that, the CPU has to ask your RAM for it. The CPU will read chunks of memory at a time and actually reading from the RAM is kind of a slow operation, so there are a few caches in between the CPU and your RAM. And what will happen is the CPU will say I want to read a chunk into the cache. Hopefully when it is processing, all of the data we will be dealing with is out of the CPU caches. So, in this case we read this memory here. We have allocated memory and we happened to get some free memory, too. These are read into the CPU cache. As our program is going along processing the data in the memory, maybe it gets along to here and it's like oh, well, I don't need the stuff in the free memory. I need a different address and I need to go somewhere else and get it. What that means is maybe we have to hop over here and read over there and continue with our program. Doing this hop and reading memory from RAM is a slow operation, so if we can reduce that, then we'll have a faster program. If we are rearranging memory like this, hopefully we'll rearrange memory such that when we read out of memory, we'll read everything we need into a CPU cache and we'll be able to process all the results. That is a feature or what do you call it? The attribute of this is called good locality. If we have all of the data in the same kind of area, we call that good locality. Another reason is copy on write friendliness. We use Unicorn and it saves memory by using a copy on write technique and compaction will increase the efficiency of this particular technique. What this technique is, is we create a parent process. We boot up our Rails app and fork off a bunch of child processes. Those child processes get a copy of the parent process's memory. Let's say we do this and say okay. We have a child process. Now when the child process is created, it doesn't actually copy all of the parent's memory into the child process. Instead it points at the parent process. So, it creates virtual memory that points at the parent process. Now, let's say we need to write some data, the child wants to write some data into its pages. What will happen is the child process will say okay, I want to write into a free memory chunk. It'll write into this free memory chunk and that connection will get removed. So, it's no longer connected to the parent process. So, the child process will copy that chunk down into itself and then write into that location. Now, unfortunately, when this happens, the operating system takes care of this for us, and the operating system doesn't copy just what we need. It copies them in multiples of page sizes. So, that means that instead of just copying that free chunk down into the child process, we may actually get some stuff on the left or right of it that we don't actually need, so instead maybe we copy this allocated memory down into the child process as well. That means now our child process is consuming more memory than we originally could have. If we were to eliminate that fragmentation, it's less likely that we would encounter situations like this. The solution to all of these problems is essentially eliminating fragmentation, and fragmentation compaction is the solution to fragmentation. That's it. Just to recap, fragmented memory looks something like this. When we compact it to have no fragmentation, it looks something like this. Now another thing that I need to cover as well is in Ruby we can think of our programs as having two kind of heaps. So let's imagine we have our computer system. Our computer system has some amount of memory. Now in that memory, when we allocate memory from the system, we typically use Malloc to do that. We'll create what I call a Malloc heap. So, we ask malloc for memory. And inside of that malloc heap is Ruby's object heap. We have two different ones, but one is inside of the other. So, Ruby's object heap is allocated by malloc as well. But we can think of them as two separate areas. When we allocate objects, they get allocated out of Ruby's object heap. We do object.new, that comes out of Ruby's object heap like this. Now ideally the Ruby heap and the malloc heap would be exactly the same size, but unfortunately they're not. And I'll give you a simple example of why they're not. Let's say they have a string. We allocate a new string. This string actually points at a byte array, and that byte array is allocated out of malloc. So, it's allocated out of Ruby's object heap but the actual string itself is allocated via malloc. This is one reason why the malloc heap will be larger than Ruby's object heap. Unfortunately, fragmentation can occur in both of these heaps. When you talk about eliminating fragmentation, you need to be specific about which one you're talking about. So, for the malloc heap, at work we typically use jemalloc, and I'm not going to get into this, but if you have a Rails application, you should use anything but glibc. So Ruby's heap, let's take a look at Ruby's heap. And I don't mean the system memory or the malloc heap. I'm talking about what's stored inside of there. This is ruby's heap. Ruby's heap layout looks something like this. Ruby's objects are represented by a specific amount of memory. One of these boxes represents a byte. So, each object is 40 bytes. Each chunk is a slot. The slots can be empty or filled. So, we'll use white to represent empty and blue to represent filled. And later I will represent a new color, orange, and that's going to represent moved. Now one page, these slots are stored on what's called a page and one page is about 16 kilobytes so here we have contiguous slots and they create a page. One page is 16 kilobytes, and a Ruby heap is made up of multiple pages. So, this is what a Ruby heap looks like. We have multiple pages here, and each of those pages is allocated using the malloc system call. Now there's one more bit of information that we need to know before we can actually implement compaction and that's that each slot in Ruby's heap has a unique address. So, if we look at all of these slots, each of them has a unique address. Given this information, we can actually implement a compaction algorithm. So, let's look at the algorithm I choose. The one I'm going to use is called a two finger compaction algorithm. It was originally done in LISP. It is not a very good algorithm. If we have time after, I will tell you why. If you want to ask me a question, say Aaron, why is this not a good algorithm and I will be happy to explain this to you. When I first started this project, I didn't think it was possible, so I wanted to choose something easy to start with. Essentially the algorithm has two different parts. The first is moving objects and the second is updating references. First we will look at moving objects. So, imagine we have a heap that looks something like this. We've got a bunch of objects in there. The algorithm works by having two different fingers or pointers, one to either side of the heap. One pointer is called the free pointer, and we'll put that at the left side and the other pointer is called the scan pointer and we'll put that at the right side. The free pointer moves to the right until it can find a free slot. It will go along like this, finds a free spot and stops. The scan will move to the left until it finds a filled slot and then it will stop. Once they're in place, it will swap the two locations. It swaps like this. And we leave a forwarding address in place of the old object. So, here this one was at 9, but it is now at 4. So, we'll leave the number four here. And we'll repeat this process. We'll move the free pointer over. We'll move the scan pointer back. Swap the two. Leave a forwarding address, and then we repeat this process until the two pointers have met. So once the two have met, we are done moving objects around. The next part is updating references. So, let's say we have that same heap. It looks something like this. After objects are moved it will look like this. What we do to update the references is we walk through each object looking at the references and updating them. So, for example we start by looking at one. One points at six, but it's fine. It's not a forwarding address. So, we move on to two and two points at nine but nine is now at four so we update it to point at four. So, we say okay. Point over here at four. And we do that for three as well. Three points at eight, but it should go to five, so we update that. Thank you. And then we continue this process for each of the objects in the heap, and then we're done updating references, and all we have left to do is we convert those moved addresses into free slots, and we've successfully compacted the heap. This is it. So, if we were to rewrite this algorithm in Ruby, this is a Ruby conference, not a C conference... right? Though I will give you pointers in C if you would like. [ Laughter ] Sorry! Okay. So, this is what the algorithm would look like. The left and right (((TEXT MISSING DUE TO SOFTWARE CRASH DURING TALK))) So for example, we need to know how do hashes hold references, how do arrays hold references, how do objects hold references, expressions, structs, etc. So, this is actually the most complicated part of this patch. So, I'm gonna show you the actual code here. Sorry. It's C code. This is it. One sec. Here is the entire patch. And that is only the reference updating part of this. If I was to estimate, I would say maybe 80% of the patch is just updating references. So, since updating references is the most complicated part of this, I want to talk about this part of the project. This is probably the hardest part for me to implement, and the main thing is we need to be able to update references while supporting C extensions. And I came up with a scheme for doing this and, I think this scheme is the novel idea. Which isn't trust me, it's not that novel. So, we have to answer a question like where are references stored? The most difficult thing about updating references is figuring out where all of them are stored. Now today, regardless of whether or not you have any Ruby core files on your computer, we are all part of the Ruby core team today, okay? All of us. Now a nice thing for us, while we are all hacking on this garbage collector is that we can say, okay, how do an array store its references? So for example, we have an array here, and we can go look at the source code for array. And if we go up array.c, we can see that the array points at a buffer. This buffer just contains a buffer of value stars, and it points at a bunch of different objects. Since all of us know how this is implemented, we can actually go into the garbage collector and say okay, when it's time to update references, we know how the references are stored so we can actually go in and write the code to fix them up. We can do the same thing for hashes as well. We know that a hash will point to a list of values and a list of keys. And we can look at the implementation of a hash and since we all know the implementation of the hash, we can write the reference updating code. And we just repeat this process for every single class that's in Ruby. So, we do it for strings, classes, modules, symbols, reg xs, all of the fun stuff. What I like to call the types is I like to call these types known types. So, the garbage collector can update all known types. Known types are types that are implemented by Ruby, by Ruby the language. But that leaves a question for us. What about unknown types and I refer to unknown types as types that are implemented in C. And I will give you an example of one. We use a JSON parser called yajl. Yajl is written in C and this is the struct that is used for that parser. You don't need to understand C to get this. I will explain it. But essentially these two values here, the struct points at two values. These are Ruby objects. It has references to two Ruby objects, okay? When we create a new yajl parser, we will Malloc the struct. Then we will also allocate a Ruby object allocated out of the Ruby heap. This Ruby object will point at that Malloc's data and this is what we deal with in our Ruby programs. Now this struct will point it has two references as we noticed here in the struct definition. It points at a builder stack and a parse_complete_callback. I don't know what they are, but what's important is they are two Ruby objects. Now unfortunately, the garbage collector doesn't know anything about this struct. Since it doesn't know, it can't update the references. So, how do we prevent those objects from moving? Now the garbage collector will look and say I don't know what this is. I can't fix it. Unfortunately, if any of these objects move, say that one moves, now it's moved somewhere else and the program will just crash. Hopefully. [ Laughter ] Yes. Let me I am going to convey to you how scary this is. Imagine okay. I'm going off script here. Imagine that Ruby object moves, okay? So it moves. It goes away. GC says I don't know. This thing moves away. Imagine the program continues to run and it doesn't crash. Like we don't use the agile for a little bit. Another object gets allocated, another Ruby object gets allocated but allocated in its place. Now the reference is good but it's pointing at the wrong thing. So just imagine that in your program. So, yes. This is scary. So, we really, really want to prevent this from happening. All right. So how do we do that? The way that we do that is all of these c extensions need to implement a mark function. They have to keep these references alive. If they don't keep the references alive, the garbage collector will of course collect them, and then the program will crash anyway. So, C extension authors need to write a mark function that marks the references. And this is from the yajl C extension and it's marking its references here. We can say every time we mark something, the C extension author has to call rb_gc_mark and they pass it in. So, these objects go through rb_gc_mark, and anything that passes through rb_gc will not allow it to move. So, anything marked with rb_gc_mark cannot move. I had to add something called pinning bits. It's a bit table that sits alongside of the objects. So, let's say is we have an object layout that looks like this. We have an array at the top. I want to demonstrate the difference between a known and unknown type. During the mark phase, we'll go through and mark this. The GC will mark, yajl will mark those in the pin bit table. When the array goes to mark, it will mark its references, but it doesn't use that function, it uses a different function. One we have implemented in Ruby and is private. They get marked but not pinned in the pin bit table. Now when the garbage collector goes to compact, we go and do the same algorithm, so we'll scan along, but what will happen is when the scan pointer finds something that's pinned, it just skips it and goes to the next one. So, all the objects that were pinned, they don't move, and we just continue with this algorithm as normal, swapping, leaving forwarding addresses, etc. Okay. Great. Good job, keynote. And it's that fast, too, GC. So when we go through and update the references, the GC will look at yajl and say I don't know this type. I can't update it. But it doesn't matter because we've guaranteed that all of its references stay put. They don't move. So, it will update the array. The array gets updated as normal. All the objects get updated as normal. Okay. Bleh. Thank you. That fast. So known types will use gc_mark_no_pin. I have changed it to movable. I think that's better than no_pin. So, we know not to move those. This is how we can keep our c extensions safe, and this is the novel idea, I guess. So I want to talk a little bit about allowing movement in C extensions. We would like to write extensions that are compaction friendly. So, what I did to do that is I implemented three things. Compaction callback, movable marking, and a new location function. The GC can't update C extensions, but C extensions, they can update themselves. So, what I did is I said okay. We're going to update yajl. Here we specify a mark function and a free function. We'll update this to have a third callback which is the compact function. This is updated code. We specified compact callback. This gets called after every compaction. Now we change the mark function, rather than calling RBGC mark. That says I want you to mark this but it's okay if the object moves. Then inside of our compaction callback, we call this function, rb_gc_location and this asks the garbage collector hey, if this thing moved, give me the updated address. And that's how we implement that. There is one known issue, and this is kind of a doozy. Let's say we have an object graph that looks like this. We have an object implemented in Ruby and one implemented in C. Imagine I am a C extension author, and I know that my Ruby object, it points at this third object and I know that the C object also points at this third object, and I know that the Ruby object will keep this third object alive. So, I think to myself, ah, I am clever. I know that the Ruby object will keep the third object alive, so I'm not going to mark it from the C object. I will rely on that Ruby object to keep it alive. I know that this one is automatically marked via gc_mark_no_pin, so I'm not going to mark this one because it will stay alive. It is a thing. So, what will happen in this case is the compactor will run, will swap locations, everything will look just fine. The Ruby object, its references will get updated correctly, but the C object, its references will not get updated. It will point to a free location or in a very, very worse case scenario, the wrong object. And our program will actually, hopefully crash. We want it to crash in this situation. So maybe this isn't too common, although I have found it in a few places so far, one of them is the compilation process. When we convert text to an abstract syntax tree, there is a location. I fixed it in instruction sequences and the intermediate representation, and we will talk about that now. Essentially inside of our instruction sequences we point at real Ruby objects. If you look at an instruction sequence, it's an array of bytes and it has a pointer to a literal. A string. It used to be we would keep this alive via a mark array. They would be relied on to keep these things alive and this pattern is almost the same as we saw in our problem object graph. So, I fixed this by essentially walking all of the instruction sequences at mark time. What that means is that we're able to eliminate this mark array and directly mark these things. And as you can see, like, this is to demonstrate this has been a multiyear yak shave, this was released in Ruby 2.6 and done in preparation of implementing the compacter. During compilation it would break and I was able to fix it in basically the same way. The intermediate representation had almost the exact same pattern used there. Those are all in Ruby 2.7. So, that is great. All right. One more example. I found this in the JSON library as well. An interesting pattern about this problem is that if these two objects are written in Ruby, we have pure Ruby implementations here, if we are clever and by clever, I mean evil Ruby developers, we can actually cut that reference, because it's just Ruby code, right? And if we cut that reference, the garbage collector will go ahead and collect that object and now we have a C extension that's pointing at nothing and the program will crash. So, let me give you an example. Here is a problem code here in the JSON library. We're pointing at constants and the constants are globals. I could make this program crash by doing this. Essentially what I would say is okay. Guess what? I am going to remove that constant here. The garbage collector would then go ahead and collect that constant when we did a gc.start and then the program would crash. So that was cool. The fix is very simple. We just say we need to mark these things. I found the same thing in message pack. Since this reference was created in Ruby, I was able to cut it, make the program crash, and then let's see did I do the animation? Yes! Ahhhh! I love that. I'm trying to use all of the Keynote animations. All right. So my hypothesis with this, and I think that most of you will agree with me is that pure Ruby programs shouldn't crash. Right? [ Laughter ] Yes! Yes! Clap for that. Yes! [ Applause ] I know we all want to be on the Ruby core team, but our programs really shouldn't crash. We should not get core files out of them. Since I was able to get the rest of the core team to agree with this particular premise and because of that if I can make your program crash using pure Ruby, it is a bug and we should fix it. What shakes out of this is a very, very simple rule for C extension authors, and that is essentially if you hold a reference, you must mark the reference. That's it. If you hold a reference, you need to mark a reference. Now let me give you another solution, another solution to this problem is to just don't write any C code. I think that that is the better solution. So, let's go over one more challenge and check out the results. I want to talk about object_id. This is an interesting problem. Generally direct memory access prevents movement of objects, so let's take a look at how object_id is implemented in Ruby 2.6 and older. Object_id in Ruby 2.6 and older is based on location. Now, the question is if one of these objects moves, what is the object_id now? Right? And I'm gonna talk about my initial solution for this, and we'll see a much better solution here in a couple minutes. Essentially, what is the object_id here? We would say we don't know. We haven't looked. We compact the heap. It moves. Check out the object_id. It is 1. No problem. What is it after we move? We will look here. Okay. Create an object. Look at its object_id, it's 4. We compact the heap. Then we check the object_id again, question is what should the object_id be here? Well, you would probably expect 4. I would think. So to fix this, my initial solution was to keep track of that object_id and make sure it didn't change. So, essentially I keep a global map. If you call object_id, we keep track of the object_id that you saw. Okay? So essentially the solution is here, walking through the code again, check the object_id, create a map, 4 maps to 4. We compact the heap. Now it's at address 1. So, the address 1 maps to object_id 4 and when you call again, you get the right number. Unfortunately, this introduces a problem, and this is a thing that sucks about this algorithm. What happens here, let's say we take a look at this and move this and allocate a new object. This object y happens to be put into slot 4. And now what? What should the object_id be? In my initial implementation what I did is said okay. We're just going to count up. Just add one. If that one's being used, go up again. Just keep going and see what happens. [ Laughter ] And then store that. So, in this particular case our object_id is gonna be 5. Let's walk through these animations. Yay! I can't wait to get to the better solution, because this one is not good. So, here we have object_id 5. Unfortunately that means we have to clean up the table when an object gets freed or the table would grow forever. So, in GC clean up, if the location was seen, we have to remove it from the map. I call this mostly location based object IDs, because it's typically the address, but sometimes it won't be. Fortunately, this is canceled. Well, it's like 20% canceled. Actually, we refactored it to be better. That's what we did. Now in master, I don't know if this is actually going to make it into 2.7, but in master we have a monotonic object ID and I worked on this with my co worker, j Hawthorne. So, when you call object ID, we will give you a number and when you call we will give you a bigger number and we will do it forever and ever and ever. This is what it looks like if you build Ruby today, you call object_id new. I gave an example here in the middle where we allocate an object, But you will see each time it increments by 20. But that final one, the one in the middle there, it didn't get an object_id, and the reason I did that is to demonstrate that this number is only calculated at the time that you call object_id. So, a cool feature about this is object_ids are truly unique. Before since they were memory addresses, it meant that we could reuse them. Today they are unique. We can also do weird things like count the number of object ID calls. So, for example here, the other thing we can do is I didn't put a slide of counting object IDs, but I want to call something out that's weird. In Ruby 2.6 and older, if you call in spec, it is actually the address. Those two numbers are related. Now in Ruby 2.7 and newer, there is absolutely no relationship between those two numbers. And let's cut this part out of the video, but a question I have for you in the audience here is if that inspect is based on the address and the object moves, what will the inspect look like? But please don't file a bug about that. Thank you. Okay. So, the tl;dr is please don't use object ID unless you need to. Oh my God, I'm way out of time. This is a basic Rails application. I mapped the memory out. This is the first implementation, after compaction and before. This is our Rails application at work. The top is before bottom is after. It's way too big to fit on a slide so I slide it all the way like this. This is the very first implementation when I first got it working. And this first implementation, 3% of the objects were pinned in the heap. I have since been able to improve it. I didn't tell you this. So, red dots represent objects that are pinned. Black represents objects, white represents free space. So the red dots, you can see from the red how much of the heap is pinned. You can see we have a lot of it here. What's weird is that a lot is only 3%. So, here is after some work on it before and after. And you can hardly see any red now. So, you can see it's shrunk down a bunch. That's with 1% pinned. Right now, this is what it is today. Actually it may be even better than this. So, you can see there we have a much bigger improvement. [ Applause ] Yes, thank you! 3% pinned results in a 10% smaller heap. Future plans. I'm way over time. I need to do performance improvements. It's not fast. It's very inefficient. We do a full collection and then move the objects and then update the references and then a full GC. I have eliminated the full GC. That is gone. We still do the three steps but we can reduce down to two and it is theoretically possible to combine those two together into one. So, I want to do that as well. In Ruby 3.0, my plan is to add automatic compaction. Right now you need to call GC.compact. In the future, you do nothing. The difference between what we have today in sliding, instead of using two fingers, we will slide them together like that. The reason is for better locality and it supports variable width objects. And the final thing I want to do is implement variable width allocation. And what that means, oh, thank you, yes. What that means is today we have fixed width allocation that looks like this. Our heap looks like this and instead I would like it to look like this where we can allocate things of any size that we want to. The way I want to end this talk today is we're constantly improving Ruby. The folks on the core team are doing their best to improve the Ruby implementation that we have now. I personally believe that Ruby's future is very exciting. And I want to say thank you to all of you for coming today. Thank you. [ Applause ]
Info
Channel: Confreaks
Views: 3,707
Rating: 5 out of 5
Keywords: Ruby, RubyConf, Programming
Id: 1F3gXYhQsAY
Channel Id: undefined
Length: 46min 32sec (2792 seconds)
Published: Fri Nov 29 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.