 We should start now, because it is time to do that. One sec, let me take a photo. I got to use this selfie stick. All right, thank you, thank you. So I just want to say thanks to Justin for giving that really incredible talk earlier. It was actually really good, so everybody give him a round of applause. It's definitely the talk that we all want our coworkers to watch. All the ones that are not here and not watching the live stream. Anyway, so before I get started, I want you to know that me, Justin, and Gary, we all met before RubyConf, and we swapped all of our slides. So I'm going to be giving, like Justin did a really good job, but that was my presentation that he gave. I'm going to be giving either his talk or Gary's talk. I'm not sure which one yet. I haven't actually seen the slide, so please bear with me. All right, so I'm going to, let's try to do this. OK, so JavaScript. I'm not sure if this is, I'm not sure whose talk this is yet. Could be Gary's, could be Justin's. OK, let's go to the next slide and see what that is. I can't tell, I really can't tell. All right, all right, let's get to some serious business. This talk is called Ruby VM Turtles, the TMI edition. My name is Aaron Patterson. You know me on the internet as TenderLove. If you don't recognize me, like I look different than I do online. This is what I look like online. In case you don't recognize me here. This is my cat, Gorbachev Puff Puff Thunder Horse. I've got stickers of him, so if you'd like a sticker, you can come say hello to me, and I will give you a sticker of him. This is my other cat, SeaTac. Facebook, YouTube, Instagram, that's her name. We call her Choo Choo. We also have a sticker of her now, finally. So you can get that one too. I'm on the Ruby Core team and I'm on the Rails Core team. This doesn't mean that I know what I'm talking about. It just means that I'm very bad at saying no. This is very, this is true. So I work at Red Hat. I'm an engineer at Red Hat. I am on the Manage IQ team, and we build an open source application that manages clouds. So if you have a cloud at your work, we can manage it with our software. We manage anything from regular clouds to rainy clouds to snowing clouds. All of these, we can manage those. And the application is open source, you can go check it out here on the GitHub's. I signed up for the RubyConf 5K. Anyone else do this? Yes. So I signed up by accident. I thought it was a retirement plan. It turns out it's just a bunch of people who are going to run. And there's literally no point. Anyway, I'm really, I'm really, really excited to be here in Texas. It's a really great place. I'm glad to be here in San Antonio. I'm excited to be here in San Antonio because I heard that like, San Antonio is really famous for ice cream. I don't know if you know this. They're famous for ice cream. So my wife and I arrived last night, we went to dinner, and I decided to order dessert and I wanted to get pie. There was no pie emoji, so I just put a pizza pie in there. So I wanted to get a pie, but I was afraid that the waiter might forget the ice cream. So I said to him, remember the alimogic. I've been laughing at that one all night. Like, hey, Abby, listen to this, listen to this. I'm gonna say this in my talk. No, really. All right, okay. So let's do this, let's do this for real. All right, I was gonna name this talk Stupid Ruby VM Tricks, but then I realized that the tricks aren't very stupid, so that didn't make sense. And then they're also not tricks. So it was just Ruby VM. So I'm gonna talk about Ruby's virtual machine and its internals, and in case you can't tell, I'm very, very nervous. I've never given this talk before, and so I'm scared, but we will get through this together and the upshot is that I'm right before lunch, so if I end too quickly, everyone will be happy. We get to go to lunch anyway. Also, this is the very first day, so by the end of the conference, everyone will have forgotten about my talk, so it doesn't matter how poorly I did anyway. Plus I probably also won't die on stage, hopefully. Though if I did die, I would be dead and it wouldn't matter how bad I did. So warning, this is a tech talk, there's actually a lot of code in here, I apologize in advance, there will be code, and not all of it will be Ruby, much of it will actually be C code, so I'm sorry. So we're gonna talk about Ruby's virtual machine, we're gonna talk about its internals, and this is true except that this talk is actually a talk about failure and how I failed. I decided that I would try to write in a head of time compiler, I thought that would be really, really fun. I'm gonna do that and I failed at doing that. So this talk is going to be about that, one of the things that I learned on my way and I don't think it's actually a failure, I'm just not finished yet, so I wanna rebrand failure as potential for success, so it's there, it's going, I know how to do it, it's just not done yet. But I also thought, I really think data analysis is kind of a cool thing, although after this morning's keynote, I'm worried about the data analysis that I did, I decided to write down, do a time breakdown of my feeling of being successful, like when do I feel successful, I decided to log all that and figure it out, so I've actually broken that down into a pie chart and this is what it looks like, and you can tell that this pie chart because one of those, one of the pie pieces is actually extracted and moved outside so you know that this is for real here. Anyway, so I mean, all right, I said I failed, but at least I learned something, right? I learned something, does that matter? The answer, no. All right, so let's dive in, let's dive into this. As I was thinking about this, I was thinking, all right, we're gonna do some head of time compiling and what does it mean when we do Ruby in my program? We run, say Ruby, run my program, what does that mean? So I was sitting there thinking about it, I'm like, well, okay, so when we think about running a Ruby program, we can actually break that down into two distinct steps. There's two distinct things that happen when you run a Ruby program, and these are the two things. The stuff that happens before the program runs and then running the actual program, those are our two distinct things there, and you'll notice that one thing naturally goes to the other, we say, okay, well, the stuff that happens before the program runs, that happens before running the program. Then we run the actual program and then the program goes, but actually, this is actually a loop too. I'm using some funny terminology here, but there is a grain of truth to this, so we say, okay, well, there's some stuff that happens before the program runs, like parse and compiling, et cetera. Then we run the actual program, and actually, this actually happens in a loop. If you think about it, like anytime you do eval, which you're not doing your code, please don't write eval in your code, but if you do do eval, you're essentially going back to the beginning of this thing here as well, right? You're doing that and you may be doing this in a loop over and over, whatever it is, depending on your program. Now, if we think about the actual details of what these two steps are, we have on the left here, we have the lexer, compiler, parser, and on the right side, we have the virtual machine, and I don't really wanna talk about the lexer and parser. I'm gonna dedicate about two slides each to those things. Most of the time, I wanna spend on the stuff that happens between the parser and the compiler, right in there, that white space, then the compiler itself and also the virtual machine. So I'm gonna talk about the lexer now. This is the lexer, it exists. All it does is it takes your program there on the left, which is a string, and it turns it into some tokens for you, that is it. Those come out on the right side, all right. So that's all it does, turns those into tokens. There you go, done. Now, the parser, the parser, it also exists, and what it does is it takes those tokens and tries to make sense of them and actually converts them into an AST on the right here. Okay, there we go, great, it exists as well. And if you wanna know more about this stuff, I'm not gonna talk about this stuff, but if you wanna know more about it, you should go to so many H's talk at 305 today. She is going to talk about this very detailed, I've seen her talk and it's very good, I recommend it. So at this point, this is where Ruby 1.8 and Ruby 1.9 diverge. So Ruby 1.8 interprets the AST, so what happens in Ruby 1.8 is we actually have this tree representation of the code, and the interpreter just walks along and says, okay, well, we made a method call, and we were doing array.each as in the previous example, so we have to figure out what is array, then we walk back up to the method call, then we go down and call .each on that, and then we call into the block, and then we go back up to the top. That is the way the interpreter worked in Ruby 1.8. With Ruby 1.9, we switched to using a virtual machine, YARV, which stands for yet another Ruby virtual machine, and then my parentheses VM there is kind of redundant, so it's yet another virtual machine, virtual machine. But anyway, Ruby 1.9 switched to a virtual machine, and what happens here is we actually have a compile phase where we take that AST, and we turn it into some codes right there, some instructions, and we actually evaluate those instructions rather than walking in AST. So, what is a virtual machine? Well, I'm glad you asked that question, because a virtual machine is like a real machine, but it's virtual. Gotta kill some time here, I'm so nervous. All right, so we have a computer, let's say we have a real computer, there's a real machine with real code, it's got some assembly language on the right there. These instructions are actually defined by the chip that's inside the machine itself. It's a real machine, we have real instructions, and this chip defines those instructions. The capabilities of this assembly language are defined by the capabilities of the processor. Now, what's interesting about virtual machines is that virtual machines give us freedom. The reason they give us freedom is because they're essentially imaginary. We're making these up, they're virtual, they don't necessarily exist, we implement these machines in software, but these machines can have any instructions that we want them to have. They give us the freedom to innovate on the machines themselves. So, we can sit around, and this is actually a picture of my cat, imagining her virtual machine, just thinking what instructions should I imagine up to date? So, there are two types of virtual machines. There is stack-based and register-based virtual machines. You should go look up register-based virtual machines on Wikipedia later. We're only gonna talk about stack-based VMs because that's what Ruby's virtual machine is. Ruby is virtual machine is a stack-based VM. So, you think to yourself, what is a stack-based VM? What is that? A stack-based VM is very much like a calculator, okay? It's very much like a calculator that I use, which is an HP, and I use an HP all throughout high school and then into college before I dropped out of college, and the reason that I use an HP calculator is because it says it's rad right there. You can see, you can see this calculator is rad. Anyway, so let's, for those of you that have not used an HP calculator, this is what it's like to use one. Let's say we wanna do, we wanna perform this calculation nine times 18. The way that you actually do this is you say, okay, on the HP, you go, all right, nine, you hit nine, then you hit enter, then you hit 18, and then you hit enter. And what happens is you end up with two numbers that are on the stack there, in the lower right, you'll see nine and 18. Then you hit the star key, and then that multiplies them. It pops those two numbers off the stack and then pushes the multiplied number back onto the stack. Now, you may be thinking to yourself, that's so much work, why bother doing all of that? And my answer to you is go back to your TI-83 plus. We don't want you here. Anyway, so, all right. Actually, one of the other reasons that I liked having an HP calculator in high school is that no one would borrow it from me. They say, oh, can I borrow your calculator? Yes, do you know how it works? Yes, of course, it's a calculator. Are you sure you know how it works? Let me show you how it works. Why would you do that? I don't want to borrow your calculator, okay, cool. Anyway, so if we think about this calculator, the calculator has instructions. These are our instructions. We're doing nine enter, 18 enter, and times. And when we run that, we see that it's working with a stack. We say nine enter and it pushes that nine onto a stack. So we actually have a stack here on the right. We push that 18 on, and we see that that pushes those numbers up the stack. And when we hit times, it pops those numbers off the stack and pushes the resulting value back onto the stack. And if you look at these instructions on the left-hand side, we can actually directly translate those into YARV instructions, into VM instructions. So the instructions on the left are equivalent to the instructions on the right. So except on the left-hand side, that's our calculator. On the right-hand side, that's our virtual machine. So when you're thinking about virtual machines, virtual machines should not intimidate you. It's just like using your HP calculator. The thing that should intimidate you is C code. C code shouldn't intimidate you. Sucks. It's not very fun. Anyway, so we have our YARV code here. And when we look at this YARV code, this is these three instructions. This is our program. I like to think about this as our program. And our program is just an array of instructions that we're just iterating through that array. And that number, that nine and 18, those are instruction parameters. And then on the left side, those are the instruction names. So put object as a name, op mult as a name. All right. So how do we get this machine code? We get the machine code through the compiler, Ruby's compiler, and Ruby's compiler is actually a multi-pass compiler. We're gonna look at the different steps in the different steps that the compiler makes. We go through these particular steps. We have an AST, which goes to a linked list. We perform optimizations on the linked list and we end up with byte code coming out at the end and we execute that byte code in the virtual machine. So we can actually access that byte code through this class called Ruby VM. Instruction sequence is available on your Ruby's today. And we're gonna be following that code through and seeing what it actually does when we compile code. It's essentially the same thing. Evaluating code with this is essentially the same thing that the virtual machine does when you do RubyMyProgram.rb. So the way you use it is just like this. You can say, okay, pass it a block of code or pass it a string of code and then disassemble it. So if I run this, if I run this code and print it out, the results will look like this. You don't need to read it. That's just all of the instructions for that particular chunk of code. So if you're curious about what instructions that code is equivalent to, you can just run this and find out. All right, so the first step, the first step is to get an AST. Now what is an AST? AST stands for abstract syntax tree and the way that we can get that is via this function called rb-compile-string. Now the return value there, this is C code by the way, that return value node, node is actually an AST node. It's just one of them. It's actually the top of the AST. So if you look at the AST, if we have that particular AST, that node star is actually the top there. It's pointing at that top one, right? So the next thing we do is we translate that AST into a linked list. Okay, so what is a linked list? For those of you that don't know what a linked list is, linked lists were invented in 1836. They were actually invented at the Battle of Alamod by Alexander Graham Link. And we actually had, it's really cool, I Googled and I was actually able to find some historical photos of Alexander Graham Link. He looked like that. So actually, okay, really, really linked lists. All it is is just a list of items that are connected by pointers. And in this case, so this is a singly linked list and actually in our case, we're gonna be working with doubly linked lists so they actually link back up. And the place where that linked list is generated is inside this function, RBI seek compile node. And we pass it our node so that node that we got previously, the AST node, that's, we pass it in right there. Now, like any great C program, the return value is actually right there, one of the parameters. Not only that, but that's actually our final product. That's actually the compiled down byte code itself, right? We're gonna actually have to dive into this. This function is actually responsible for generating the linked list, doing any optimizations on the code, and then actually creating the byte code itself. So one function with one responsibility, right? Love that. Anyway, so if we dive into that function, we'll see that we end up with this. There's a bunch of branches inside that I'm just showing an excerpt here. And all those branches are based on the ND type of that node. Now, what is ND type? Okay, I don't know what this is, so. And by the way, this is my thought process as I'm going through reading this code. And unfortunately, like I was watching Justin's graph of all the different time things and he's like, oh, this part gets bigger and this part gets bigger. And I'm like, oh, man, I wish mine was that small. Anyway, so what is ND type? ND type is very simply defined as this. Basically, what we really care about is there is a flags member of the struct that gets passed in. Now, we're looking at the C code and we don't know what that flag, what is that flags value? We can't really tell, so I decided to do something dangerous. We're gonna do something extremely dangerous and we're actually going to reach inside Ruby itself, get a handle to the function that compiles all of our stuff down to an AST and actually call that from Ruby. So if we do that, here's an FFI program that actually gets a handle to RB Compile String, calls it with some stuff and then prints out our node. So we'll say right there is RB Compile String, we're getting a handle to that C function itself, which we're not supposed to do. And this code will probably not work on your installations. I compiled my Ruby with like O1 and dash G, so all the symbols are available. This might actually be gone on yours. Anyway, we're gonna call it with an empty file name with some code and then we're gonna print out what the pointer is that returns and unfortunately we get an error but that's fine because it dumped out the flag value which is one. Literally sitting at the computer doing that meme. All right, so this, all right, now which branch do we follow? We know what ND type is gonna return, it's gonna return one. So if I look up node scope, that value equals equals node scope, I go find that and I see that it's inside of an enum. That's actually the very first one that's in the enum so it's equal to one, yay, we found it, great. So this is the branch that it actually follows. It follows through a switch statement down to this default switch statement that calls compile. Now compile is a macro and I didn't put the macro into these slides because I really don't wanna hurt you that much before lunch but basically what it does is this kicks off the recursive process of the ASTs. We're actually gonna walk that AST recursively and produce a linked list. The main function that it calls is it calls ic compile each. This is our recursive function. Now the very top of the function looks like this. It returns an instruction sequence, takes compiled node and it walks all the way through it. Now I didn't put this in the slide because this one function is over 2,300 lines long. So let's look at, this one function is over 2,300 lines long and it handles every single type of AST node that you get in your AST. So let's just look at one, let's look at true. How does it handle true? Well this is the case statement, it's just a very giant switch statement. This is the case for true. So if AST is true or for the true value in your Ruby program, we actually call add ins one and what this does is it adds a new linked list item. So we get a new item added to our linked list. This is the object name, it's just put, or that's the instruction name, the instruction is put object and then this is the actual value. That's the parameter that we're giving to the put object instruction. So if we print out this, we print out the code so let's just say we print out ptrue and look at the instructions that come out. We can see right here we actually got put object, that was our name and you can see the parameter to that instruction is true, just like we saw in the code. So another, I wanna look at a little bit more complicated example which is if statements and you'll notice like we're not gonna walk through every one of these things but you'll notice that it actually handles each branch of the if so it looks at your conditionals, the body and then the else clause, right. The important thing to notice here is if you see the macro is called compile, those are the things that recurse and any macro that starts with add underscore, that actually adds a new link to your linked list. So if we look at the linked list, we'll see something that ends up looking like this where we have an instruction sequence put self, we're putting self up there, we're calling put object, so put self just put self onto the stack, put object puts true onto the stack and then we say call p and that pops off the stack and actually executes the function. So why use a doubly linked list? Why do we have this doubly linked list phase? The reason we have this is because we're actually gonna be doing some manipulations on this list and mutating a doubly linked list is much easier than say mutating a C array, right. We can actually link and unlink very easily with a linked list and that's where we get into the optimization step and the place we find that again is in the RBIC compile node and that calls this function called iSeqSetup. Now iSeqSetup is responsible for doing the optimizations as well as doing the bytecode. Again, it's one responsibility, right? That's great, I love it. If we look at that function, it calls iSeqOptimized and this is where our compile time optimizations happen and I'm calling these out as different than our virtual machine optimizations and we can see what those optimizations are if you run this code, Instruction Sequence Compile option, you can actually see all of the optimizations that are available in Ruby's VM. We're only gonna focus on two of these, we're gonna look at P-Poll optimizations and we're gonna look at specialized instructions. So these are two that are turned on. P-Poll optimizations are essentially eliminating dead code and I don't mean dead code like your code that doesn't execute, I mean useless instructions. So let's say we generate a bunch of instructions from this, we walk the AST, we generate a bunch of instructions and some of them happen to be useless, we can eliminate those. So that's what these P-Poll optimizations do. So for example, let's say we generate a jump, it jumps to a label and immediately after that label we do another jump, well what's the point of jumping to label one when we can just jump directly to label two, right? So this is the type of eliminations that P-Poll optimizations do, so they pull out those instructions that are useless. We also have specialized instructions and these specialized instructions are for all of your special occasions, including weddings. These random thoughts also pop into my head while I'm doing this stuff. All your special occasions. All right, anyway, so let's look at foo.bar versus foo.plusbar, okay? If we look at regular method dispatch foo.bar versus saying foo.plusbar, what is the difference? Well, in the case of foo.bar we actually have to look up that method and call it and in foo.plusbar we know that there's this method called plus and we know the location of that and maybe that's the one that we can call. So with our regular method dispatch, we'll say okay, we're just gonna send that method, we call send. Go ahead and do it. We call send for foo to figure out what foo is because it could be a method, in this case it thinks it's a method and then we call send for bar on the return value of foo, right? Now again, if we look at regular method dispatch for foo.plusbar it looks exactly the same. We're doing send for foo and then we do send for bar and then we add the two together. Okay, now what specialized instructions do is they say, well, you know, yes, it's true that calling plus is a method send but on the other hand, maybe, unless you're using Rails, hopefully plus is not monkey patched so you're probably gonna be calling the real plus method itself and we can optimize for that. So that's what specialized instructions do. So in this particular case, those sends turned into opt send without block. That used to be a send right there but now it's opt send without block. This is a specialized instruction in case you're calling the method without a block and in this case we are not. So we use that one. Again here we have that other send was translated and you'll see down here we're doing opt plus instead of calling a send. Before we had three sends, now we have these three specialized instructions. So these specialized instructions do less work. Okay, so as a reminder, we haven't actually run any Ruby code yet. This is all in the stuff that happens before your code runs phase. So all right, we're gonna continue on from link lists. We have a bit more work to go. All right, byte code. So inside we're gonna pop back up the stack a second. We're inside iSequence setup and then the next thing that we need to do, the thing that actually converts our byte code or our link list into actual byte code is this function called iSequence setSequence. Very descriptive, right? We all clearly know from this function name that we're gonna make byte code here. So what is byte code? A byte code, our byte code in the Ruby VM is just a list of integers. Okay, it's all it is. It's literally just an array of integers. Those integers are actually addresses, but we'll get to that a bit later. It's just a list of numbers. And in fact, this fact will come back to byte us. And random photo, I don't know. All right, so we have raw instructions and our raw instructions actually end up in this value star. This is a list of our instructions, okay? That is just an array of integers. It's actually an array of addresses, as I said, pointers there. Anyway, we add instructions to the list. This is actually how it gets stuck into the list. So you'll see generated iSequence. We're actually inserting that byte code right into the list there. And any of those parameters, for example, we saw put object nine when we were doing the calculator example. Those are gonna go into the byte code down here. I've omitted a lot of code in here because this function handles every single type of linked list node, right? All right, so for example, we have this thing like putString, for example. This is one of our examples here. We say putString is our instruction. Foo is the instruction value. Now we've got a list of integers. Yes, we finally have our byte code. We are ready to run this stuff. We're ready to do it. This is where the virtual machine comes in. Now, we say to ourselves, okay, the virtual machine, how does the virtual machine work? We need to know what these byte codes actually do. And to find that out, go look inside of INS-NS.def. There might plus or minus some N's and S's. That is the file. It has every single instruction in it. This is the instruction layout. It has the, this is the format. It has the byte code name. That'll be the name. This is the operands of the byte code. So these two things, the source of data is from our list of integers. The instruction name and the instruction operands come from that list. Now, these other values, the pop values and the return values, those are stack manipulating values. So we're actually gonna pop things off the stack and then we're gonna push other things onto the stack. So here's an example of an actual one. This is an actual instruction. This is for the put string instruction and you can see the name of the instructions put string. We have one parameter which is the string. We don't pop anything off of the stack and we push a value onto the stack. So next we'll look at VM optimizations and these are different from our compile time optimizations and we can see the optimizations that our VM has by running this code. There's a constant called ops on there. You'll see we have direct threaded code, operand unification and inline method cache and I'm only gonna talk about direct threaded code today. You can look up the other ones online. If you wanna manipulate any of these, there's actually more optimizations you can do. Check VM core.h and you can tweak stuff in there if you want to. So let's talk about different types of virtual machines. For example, we have native execution machines which is what Ruby 1.8 was like. We just have a chunk of code and we actually just walk through and execute all of that. Okay, that's our baseline thing. The next thing is we have virtual machines that are called decode and dispatch virtual machines and the reason they're called decode and dispatch is because we have our chunk of code here that gets translated to a bunch of instructions and we have a central loop. That loop go loops through the instructions, looks up the instructions from a hash table and then goes and executes the particular function that it needs to call for that instruction. That one returns and then the thing loops back up on itself and goes and executes the next one. But we can actually do better than this. We can do better than this. We can do threaded interpretation. So we can say like, okay, well, we don't wanna do this. This loop is costly. We don't wanna be executing this loop over and over again. So what we can do is when we generate the virtual machine, we can take that central part of the loop, the part that dispatches and we can insert that at the bottom of every one of those instructions. So we can say, all right, we're actually gonna take that, put the stick it at the bottom of the instruction and as soon as we get to the bottom of that, we're gonna go execute the next one, have that lookup code right there so we can actually hop directly to the next one and continue on so we don't have that loop. All right, so the important thing to take away from this is that our VM is actually generated and that eliminates the loops. Now we can even do better than this. We can say like, well, remember, we're doing that lookup from a hash table. We have to say like, okay, put string, where does that go? We go look that up in the hash table and then we go call that function. We can do better than that. What if, what if the instruction was actually the address of the function that we wanted to call? Ooh. So now instead of going and doing that hash lookup, we can say, all right, we have the pointer, we know where that function is, let's directly jump there instead. This is called direct threaded interpretation and this is what Ruby's VM does. So we eliminate that dispatch code and we can jump directly from the end of one instruction to the next instruction. So no more lookup, we just go all the way through. So all right, cool. We've gone on a tour of the VM internals. Let's talk about failing at AOT compiling. As I was going through this, going through this trying to put together an ahead of time compiler, I thought to myself, you know what, these readings through us are byte code, it's just a list of integers, that's all it is. No problem, the integers are addresses, we'll just take that list, write it out to a file, later on we can read that file in, we can say like, this is me, oh, if it's a list, we can write it to a file, we can load the file back in and execute that list. Seems like a good plan, right? Well, unfortunately, past Aaron was not very bright. Past Aaron, he was an idiot, but he's smarter now, though it's still not too smart. So all right, let's take a look at this step in the code where we say, all right, we're gonna stick our instruction into the generated list and down here we're gonna stick our value in. Now that parameter, that parameter you may have noticed there that it's actually a value which is a heap allocated object. It's actually a Ruby object. It is a Ruby object, it is a pointer to a Ruby object. It is a heap allocated Ruby object that means that the next time we actually run the program that location is bad. It's gone away, it's different. Because the next time we allocate that string it's gonna be in a different location. We can't look it up again. The pointer always changes. So we actually have to write that object to it, write that object to the disk and then load it up later. So it's not an impossible task, it's just something that we have to overcome. So let's do end stuff here. I've been talking about VM internals. I hate giving a talk without giving some practical applications to this. So let's talk about a few practical applications and then wrap this thing up. So we can tweak optimizations. If we know that our VM is gonna be faster with some particular optimizations we can go test those out, recompile our Ruby and get our programs running a bit faster depending on those optimizations. We can understand what our code is really, really doing. So we can take that Ruby VM instruction sequence, compile it down, look at the assembly for that and understand what it's actually doing. So I encourage you to go take a look at these two examples. I'll post these slides online so you can try it out. But look at the difference between the byte code generated between those two. I think you'll be surprised at the difference when you look at them. Another thing I encourage you to do is browse iSequence compile each. I know it's 2,000 lines long or over 2,300 lines long but it's chunked up into a switch statement so you can find various constructs in there. So if you wanna understand how Ruby handles if statements or case statements or any of those or begin and any of those things you can look it up in this particular block. For example, we can look at this code and say, okay, here's a quiz to all of you. Which one of these is faster? I actually tweeted about this a little while ago. If you go through my tweets you'll find it. Which one of those is faster? I'm not gonna tell you. If we have time for Q and A maybe I'll tell you. Now that I've changed this, notice I've changed only one line. Now which one is faster? And why? Why is it faster? So if you go look through that code you can find it. So remember these things. If you're gonna remember anything from this talk go look at this file, insnsnsns.dev. ISequence compile each, iSequence compile node and if you wanna know more there's actually a YARV architecture document checked in to Ruby itself. It's called YARVarch.j. You can find it there. Unfortunately it's written in Japanese. The good news is that I can read Japanese so it's fine for me. But then I found that there was actually an English version so I opened that up YARVarch.en. This is the entire contents of the file. So if there's anything else you wanna know about it I recommend reading Ruby under a microscope. Also there's a book called Virtual Machines that's mostly about actually virtual machines themselves not programming languages but containerization. However that has a lot of stuff in common so you can learn from that as well. Thank you very much, I have stickers. Come say hi to me.