 So I want to say thanks to all the organizers. I'm really, really honored to be here. We were talking last night about how I think Goruko is the oldest regional conference. I don't think anybody has refuted that yet. And Goruko is also very special to me because it's the first regional conference I ever attended. So I'm really happy and honored to be here to speak to you today. So thank you for having me here. I just want to start off and say that the code I've broken it, it is broken. That is it. So this talk is called Code Required. I have to do every slide in seven seconds or less because there are many slides. My name is Aaron Patterson. I'm your closing keynote, and I'm the only thing that stands between you and beer right now. So I'll try to hurry it along. Oh, I also want to say thank you to the organizer for passing out those surveys before I speak. So if you haven't filled it out yet, be sure to say nice things about me. I want feedback, but only good feedback. I work for a company called Red Hat. At Red Hat, I'm on the Manage IQ team. Our team builds an application for managing containers and virtualization stuff. If you have a cloud, we will manage it. My title on the team is Hacker Man. This is me. So all of our code is open source. And you can actually go get it here. And throughout my talk, I'm going to be referring to different benchmarks that I've run and different things that I'm doing and testing. And I usually test it against our applications. So if you want to see the code that I'm testing against, you can go ahead here and check it out. I'm on the Ruby Core team. I'm also on the Rails Core team. I'm the only person that is on both teams, as far as I know. So I want to say thanks again to all of you for being here and to the organizers for having me. I really wanted to go out. I'm from Seattle. I'm from the West Coast. We don't really have as good traditional New York pizza there, so I really wanted to go and get some traditional New York-style pizza. But I didn't have time yet. So fortunately, Eileen and her husband went out and got some traditional New York pizza. Anyway, my wife is coming later today. And we have some reservations. We're going to go to this really nice Italian joint. I'm not sure where is it, Mike? In Times Square. Yeah, in Times Square. It's called Olive Garden. Maybe you've heard of it. So I'm going to go over some things I've learned at the conference here. So I'm going to talk about all the things that I've learned so far. First, I learned that we should not hate the player. We should hate the game theory. From Eileen, I learned about ABB, which means always be borrowing Eileen's code. So I learned about how to control hue lights. So this is a very enlightening talk. 25 minutes left, right? I learned about caching and the dangers of caching. And I learned that I should just be caching everything all the time at something, something, turbo links. Happy birthday. We learned about just-in-time compilers. So when I think about just-in-time compilers in JavaScript, this is what I think of. I hope that when Ruby gets a just-in-time compiler, it can be a bit more like this, a bit more classy. So from Kara, I learned about science. And the main thing that I took home from her talk is that homeopathy works. I need a doge sweater. I learned about Skynet and pair programming. And I want to show you that I do pair programming too. I'm programming a pair. Programming is hard, but teaching sounds harder. This guy works for Shopify, maybe. I'm not sure. So the point that I'm trying to make here is that we've had many amazing speakers today with incredible information and amazing slides. They're very, very stylish, just really, really cool. And what I want to get across to you is that for my talk, you need to lower your expectations, please. So Mike said to me, all the organizers have been asking, what's your talk about? What's your talk about? And he told the organizers my talk is about cats. And really, that's what it is. It's all about cats. I actually am a pro Nekoatsume player. This is my game field here. You can really see that. I have some real cats that I'm collecting at home. And this is one of them is Choo Choo. That's one cat in Gorby Puff. And if you want to have a cat like this, I have instructions for how to do it. This is how you do it. This is the normal cat. Here is a picture that I like. No reason I just like. I also like strange keyboards too. So if you like strange keyboards, you should come talk to me. This is a picture of a keyboard that I built myself. I have it here. Here's another one that I built. My cat likes it. Other cat likes it too. She likes that a lot. Also, another thing I want to share with you is that my wife and I made some holiday cards last December. But we didn't get around to sending them out. So I'm just showing the pictures at all the conferences that I go to. So this is it. Here's another one. Another one. Unfortunately, I don't actually own the copyright to these images, which you can talk to me about later. I will complain about this on the boat. Another one of my cats. Anyway, I'm an Extreme Programmer. I love Extreme Programming. And I had an opportunity while I was at RailsConf to meet Kent Beck, which was really, really amazing. This is me meeting him. There he is right there. We became fast friends. We're BFS now, which is really, really exciting. Together we're collaborating very closely. We're collaborating very closely on some new equipment for doing Extreme Programming. It's essentially Extreme Programming Safety Gear. This is it right here. Working on it at home so I can do Extreme Programming at home. Anyway, I'm really sorry. This whole talk, now we're done. I'm calming down on stage. We are through the entertainment section of this talk. Now we're going to get down to the nitty gritty. Unfortunately, this talk is all tech stuff. So it's very, very technical. And I apologize in advance for that. So welcome. You may have noticed something about my slides that the font is actually in Baskerville. And the reason that it's in Baskerville is because apparently I read online that if you use Baskerville, it's more believable. So I'm going to show you a bunch of numbers and code, and you should believe it because it's the font, clearly. Anyway, so this talk is everything you ever want to know about loading files but we're afraid to ask. Or maybe you didn't really want to know that, but you're stuck at this conference anyway. You're watching me, so you'll just endure my talk. Or code required. So I'm going to talk about loading code. I'm going to talk about performance with loading code and how we can improve performance going forward. And let's do it. So the reason I got into this issue is I was sitting there on my computer, and I compute away, and I love using IRB, and I'm sitting there because I pull that up. That's my calculator. When I need to calculate some stuff, I pull up IRB, I run IRB, and it just sat there. I was like, OK, IRB, enter. And then it was like, OK, OK, OK, prompt. And I'm like, what? Why? Why isn't this fast? Why is this not fast? I just typed IRB. That's all I did. I'm not doing anything wrong. What did I do? So it turns out the reason that IRB was slow, I started investigating this is because I have tons of gems on my machine. I have maybe, I don't know, six or 700 gems installed on my machine. And it turns out the answer to this, the answer to speeding up IRB was that it don't install so many gems. And I thought to myself, don't tell me how many gems to install. I'm going to install as many gems as I want to. I apologize for the outdated meme, but this is how I felt. I was just pissed. I'm like, I want it to be fast. It doesn't matter. I have gems who cares. IRB should be fast. Just make it fast. Just do it. Just do it. Just do it. Unfortunately, nobody will do this. Nobody does this. But we will do it. We're going to talk about it. So the other thing is that I was looking into Rails. I'm looking into boot time. A lot of what I'm going to talk about, I touched on this a little bit of my RailsConf keynote. But that was more about performance from external outside of your application, like running IRB or running different things on your system like bundle or whatever. Today I want to talk about internal stuff, stuff that's internal to your application, loading code inside of apps rather than from the outside. And you can think of this as essentially inside of your apps being anything run inside of Bundler, essentially. So I'm looking at boot time a lot. And the application that we have at work has, I ran rake stats on it. And this is about what it looks like. We have over 500 models, 83 controllers, says 292,000 lines of code. And our boot time is about 12 seconds. And the way I measured that boot time in the totally scientific process, which I learned earlier, learned about earlier in this conference, is just a run time. All I did was run time on this, this is what it looks like on our application. So what I want to talk about first is we're going to talk about how files are actually loaded in Ruby. Then we're going to talk about gems and the ecosystem surrounding gems. And then we're going to talk about speeding up, actually speeding up the code loading process. So the way that files are loaded is there are three different ways we can load files in Ruby. There's actually four. Let's talk about the fourth one on the boat. The three ones are load, require, and auto load. And these all involve a few global variables. And those few global variables are the load path, dollar load path. You may have seen this before. And all dollar load path is just a list. So whenever we do a require on a file, it looks at the load path. And we can actually modify the load path via just, we can mutate it like that. Or you can actually do a dash i on the command line. And that will also mutate the load path. So if we run with a dash i, you can see here, okay, we've mutated it. Our stuff is at the top of the list. So essentially the load path is our code to load database. We need to load some code. Where do we find that? Go look in this thing to figure out where we can find that code. So the other thing we need to know is probably all of you are aware when you do require, you can require a file twice, but it only loads the file once. So how does it do that? It does that with a variable called loaded features. This is the other global variable. And this global variable is also just a list. No, it's not just a list. But it looks like a list to you. However, internally Ruby keeps a cache that's actually a hash. So lookups are fast inside of MRI, although to us Ruby programmers, when we just look at it, it'll look like a list. So this list is our already loaded code database. This is code that's already been loaded in. So just to keep this in mind, if we need to find some code we use loaded the load path in order to determine whether or not the code has already been loaded, we look at loaded features. So with that, let's take a look at some of the methods that we use for actually loading code in Ruby. This is the first one, it's called load. And it's the easiest way to load files. It'll load the file however many times you call it, it'll load that file every single time. This is what it looks like. We can use it, do load with a full path. And if we run this code, you'll see that outputs hello world twice makes sense. It just looks up the file and actually executes the code inside that file. We can also use a relative path so we can just say we don't have to fully qualify the path. And if we do that, then we have to mutate the load path in order for load to find that. So load consults the load path in order to find files. So if we run this again, you'll see I had to specify a dash I there so that it would actually find those files. So load actually searches load path. The other thing to notice about load, which maybe you've never seen this before, but it has a second parameter there called wrap and that wrap defaults to false. So we'll try turning that on and making it true in a minute. But if we say, let's look at this test program here. First we'll load x.rb and inside of x.rb we'll define a class x and we'll print out the name of the class. And then back over in the first file, test.rb will print out the constant. So we can see what it looks like. We run it, not a problem. We get x the name of the class and then we get the actual class itself. Now if we turn that thing to true, if we say, if we change that second parameter to true, what Ruby will do is it'll actually wrap that entire file inside of an anonymous module. So when we run this again, inside the file, you'll see instead of x, it's now module blah, blah, blah, blah, blah, blah x. And then the original files is uninitialized constant x because we didn't define x at the top level. So I think this is really cool. This seems like a really cool feature. Like you might be able to say like, okay, I have a library. This library requires, I don't know, some JSON version one. This other one requires JSON version two. Maybe we could name space them inside of this anonymous module. Well unfortunately, there's a little caveat. Let's say we do this. We load three files. We say this loads x.rb, this loads that y.rb and then these print out the different names. So if we look at the order of this, we'll expect this bottom one to be printed out first, so the name of y and then we'll expect that second one to be printed out there, the name of x. So when you think about this, what do you think the output would be? Me, a reasonable person, would think that both of these will be wrapped inside of a module, but in fact they are not. Only that very first one is wrapped inside of a module. All the ones that it depends on are not wrapped inside of a module. So it only wraps that very first one. So my question is, is this useful? I don't know. It seems like I don't know what I would use this for. I've never ever seen this used in the wild. It seems like you would want to wrap everything or just remove this feature. I'm not sure what it's for. I actually talked to Matt about this particular feature and he was like, ah, I don't know. So, there you go. Anyway, summary of load. Load searches a load path. Has no interaction with the loaded features list whatsoever. Okay, our next one is require and we all know require. We've typed this into our Ruby programs. This is a very simple example of it. You can give require a full path and if you call require twice, it's only gonna print out hello world once, run the code, works just like that. Or we can give it a relative path and if we give it a relative path, it'll have the same behavior, but we have to change dash i so that it mutates the load path so it knows where to find those files. So you can see there I've added slash temp to the load path so it can find them. Now what's also interesting about require is that require will return a boolean and that boolean indicates whether or not the file's already been required, right? So it will return true indicating we loaded the file or it'll return false indicating I didn't load the file, it's already been loaded. So you can tell if you look at the return value of require. So in order for a require to know whether or not it's actually loaded the file already, it has to search loaded features. And we can see that loaded features gets mutated if we run some code like this. We'll dupe loaded features, do a require and then look at the difference after we do the require and you'll see the output from that is the file that we required. So you can see that full path there and if we run this again but we say require two things one that's x and one that's x.rb it'll have exactly the same output as before just that one file will be added to the list. And the way that it does this is it does this with load path canonicalization. It uses the load path to canonicalize any parameters that are sent to require. So it says okay that x I'm gonna go search the load path in order to canonicalize it turn it into a full path and then put that thing into loaded features. So as an example on the left side we have non-canonical requires. These are not full paths. And on the right side is a canonical require it is a full path. So the way that the logic works for requires we say hey okay is this parameter canonical? If it's not canonical then we'll canonicalize it. Then if it is canonical we'll say has it been loaded already? If it hasn't been loaded then we load it. Add it to the loaded features list and then we're done. It's already been loaded then we're just done. So canonicalization takes into account the load path. Loaded is loaded question mark looks at loaded features and then add to loaded also mutates loaded features. It's just that simple really. All right so next let's look at auto load. Auto load is another interesting way of loading code. So it looks like this you may have seen this in code before we say auto load bar and bar loads x. So we say okay bar is the constant and I want you to load x. Now what's interesting about this is it loads bar as soon as that bar constant is referenced. So right here it'll load x because we've referenced bar. All right we've referenced that constant and as soon as we do that it'll go load this file so it'll puts high and then we're done. Evaluates that and we're done. So we run this and you'll see okay yes we output high first and now we get the bar constant. Okay. So this file this particular file is governed by exactly the same rules as require. It won't load the same file twice. We can give it a full path if we want to and it canonicalizes against the load path exactly the same features that require does. So if we run this run this particular code print out bar three times you'll see it only prints out high once and then prints out all the bars. So auto load logic looks a little bit like this. As soon as the constant is referenced we say have we loaded it? If we haven't loaded it then we'll go and do the require logic for that particular file. If we have loaded it then we're just done. Okay. Now something to bear in mind here that I didn't really demonstrate is that constants are loaded as soon as they are referenced. Okay. As soon as that constant is referenced it tries to load that file. So let's look at this example one more time. We say all right we referenced bar here. So it goes and loads the file doing exactly the same thing. We say high you know prints out high and it comes down here defines foo. But then we hit bar again. We've referenced the constant again. We've referenced the constant for a second time but it's inside this file. So what do we do? Bar is referenced even though we're not done evaluating x.rb. Yet somehow this code all works. You'd imagine oh bar got referenced again we're gonna get into an infinite loop but clearly that is not what happens. That is not what happens at all. So we actually have some additional logic inside of autoload that we say okay have we loaded it? No. Are we loading it? Are we currently in the process of loading that particular file? If we're not then just continue on with the require logic. If we are in the process of loading that file then we say that we're done. Or if we've loaded it we're done. So I'm gonna get a little bit hand wavy here. But there is a, yeah transition. There's a hidden global. This means there's a hidden global inside of Ruby and that hidden global is called the loading table and you will not see this inside of your Ruby code. You can't access this, you can't access this global from Ruby at all. It's inside of MRI's C code and you'll see it in a function that looks like this. This is a loading table. This keeps track of files that are currently being loaded. So our file load steps look a little bit like this again. This is the one. This one right here, this step, is where we actually consult the loading table and what those load steps look like are, we actually acquire a lock. We add it to that loading hash. The load table is a hash. We eval the file, add it to the loaded features and then remove it from the loading hash. Then unlock. So we've talked all about, this has all been about the stuff that's built into Ruby and I wanna talk a little bit about how RubyGems plays into this. So RubyGems and require overlap a little bit and I wanna talk about that little overlap there. So we'll do that by looking at rack and we'll just require rack. Okay, so we didn't do anything special, we just required rack. How did that work? How did Ruby find rack? We didn't mutate the load path. We didn't do anything. How did it know to load rack? Well, the way that it does that is that RubyGems actually implements require and we can prove this by if you pull up IRB and say give me the method require and then look at the source location on that, you'll see that it's located somewhere inside of RubyGems error. So if we disable RubyGems, we can disable RubyGems by using this flag dash dash disable gems and we look up that method again, you'll see that the source location is nil and what that indicates is that the method was implemented in C. So this require is the one that's actually inside of Ruby, the previous one is inside of RubyGems. So how does RubyGems' require work? The code looks a little bit like this, this is just a basic overview, it's not exactly like this. The way that it works is we say okay, try Ruby's require, try the original require, if that works then we're done, great. Ruby could require it, we're good. But if it doesn't work, if it can't find that file, Ruby will raise an exception and we catch that exception and we say okay, now go try to find a gem that contains that file. If we find a gem that contains that file, mutate the load path and add that gem onto the load path. Then try the real require, try Ruby's require and then we're done. So that means the first time you require anything out of a gem, it'll actually raise an exception. So if we look at this, look at loading rack, that first line there will raise an exception, but rack gets put onto the load path and that second require will not raise an exception. So the first called a rack lock goes through this left-hand side, raises an exception and we're done. The second require, since rack is already on the load path, will actually go down the right-hand side, no exception. So we can actually do this, both requires with no exception by using the gem command. You may not wanna, please understand how gem works before you do this, don't go spreading this throughout all of your code quite yet. But what gem does is it just takes that particular gem and puts it onto the load path. That mutates the load path, then both of these requires will have no exceptions. So to tie it all together, we can see the exception in action here. If we run Ruby with dash D, dash D will print out any exceptions that have occurred inside of your application. So you can see that first require there, we get a load error on rack lock. The second require did not have an exception. Now we can also see the load path get mutated. So if we dupe the load path, require rack lock and then look at the new additions to the load path, you can see that the gem is now on the load path. You can also see that loaded features is mutated as well with rack lock. So so far we've looked at loading code, globals and Ruby gems. So I want to look at a little bit of, to understand how to increase performance with this, I want to look at Ruby gem's usage a little bit. What I wanted to understand is what a typical development environment looks like because this is going to impact the way that we can do caching and performance improvements. I wrote a little survey, this is a survey code, you can check it out. All it did is gather some stats about your system, upload them to this Google spreadsheet for me to take a look at. I gathered stuff like how many gems people use per project, how many are on their system, how many files does each gem have, what versions of Ruby gems do they have, what versions of Ruby. The reason I did this is because gem count impacts the performance, file count, we have to search through all those gems, file count impacts the performance as well because we need to search through all the files in the particular gems. So Ruby and Ruby gems versions impacts the programming, the performance techniques that I'm going to be using. So this is the collected info. Again, I would collect gem count per system, Ruby version, host OS, and I would also collect a unique ID. Unique ID is actually generated like this. It's unique in quotes, it's not totally unique. It's essentially your host name, IP address, your time zone, and maybe your home directory, and then I shot and code that. The reason is I want something that's unique but not identifiable. I don't actually care who you are and I don't really want to know who you are. That's not true, don't quote me. I do care about you. I care about who you are. I just don't need to know in the data. So then we have a unique project ID and the way that I got that is Bundler will provide, Bundler will provide an environment variable when you're running inside of Bundler and I would just grab that and shot. So duplicates were possible but very unlikely. So I got about 466 unique projects, 140 unique systems. This is the Ruby version breakdown. I'm not gonna talk about it too much but this is essentially it. We got a lot of two one users and two two users. I think that 1% there, the 230, that's me. I am the 1%. Say with my Apple watch. So implementations, implementation wise I only got JRuby and MRI people responding so you can see the breakdown system-wide or per project. When I say system-wide, that's default. Like when you run Ruby on the command line, what is it, what are you using? I looked at RubyGems upgrades. What was interesting is that 44% of the people responding had upgraded once. But only 23% were on the current version and the way that I determined that was I looked at for that particular version of Ruby, what particular version of RubyGems shipped with that Ruby. So then I would look at your current version of RubyGems and I could compare the two and determine whether or not you had upgraded. So interesting thing, projects per machine. You can see it's a very interesting curve there. It spikes way up on the right hand side. The top is 82, somebody had 82 projects on one machine. OS distribution, mostly OS 10. We had some people running Linux. Project distribution, so this is like the number of gems in each project I cared about. I wanna know how many gems do you have in your project? How many do you depend on? So our top there is nearly 300. File distribution, I wanted to know like how many files do you have in your gems? We have here the max is like 14,000 files which is interesting, I believe that is Fog. I'm guessing. System distribution, so I want to know number of gems installed system-wide. So what did that look like? Here is our gems system-wide and that's crazy, on the right hand side there's somebody with like 1,300 gems installed on their system. That's not me. But anyway, yeah, wow, like, wow. I guess 1,200 gems installed on their system. So the number of files on each system too, we had somebody, there are nearly 90,000 files installed on their system with gems. So the average project, what did the average project look like? The average project had about 100 gems and about 4,000 files. This is what we were looking at. The average system has about three projects and maybe 280 gems, around 13,000 files, okay? So now we know what we're dealing with, we know what we need to, we know the size, we can kind of figure out what the size of any caches are that we wanna generate. So let's look at some performance characteristics. I have two minutes, oh my God. I want to know as the number of gems grows, how does require change? And as the load path grows, how does require time grow? So really what I wanted to study is I wanted to study load path search time. Now this, I'm gonna show you the test here, this is the test code. Please read it very carefully. We're going to study this and talk about it on the boat. But anyway, this is the main, like most important thing. Essentially what I did is I said, okay, I'm gonna require a file and I wanna see how long it takes to require a file and I'm gonna do a best case scenario and a worst case scenario. And we know that that load path is an array so when I say best case scenario, I mean the thing that's on the very left and when I say worst case scenario, I mean the thing that's on the very right. So if we take that and graph it, this is what the graph looks like along the x-axis is the number of gems that we have activated and on the y-axis is the number of milliseconds it takes to require one file. So red line is worst case, blue line is best case. So this scales linearly with the size of the load path. So just to drive that home and make it clear, our worst case scenario is when we require foo and foo doesn't occur until all the way at the end of the load path. So it's gotta go search through all of those, right? That's our worst case scenario. Now our best case scenario is when it's, it's at the very beginning. We found it at the very beginning, right? That's our very first, our founded at the very beginning so it should be our fastest. And when you think about that, if it's always at the beginning of the array, if it's always at the beginning, the performance should be a constant time. It's always there, always takes the same amount of time no matter how long the array is. So why is the best case scenario performing in linear time? I don't, I don't actually have an answer for you. I think that this is a bug. It should be performing in constant time. We know it's always at the beginning. You would expect the worst case scenario to be linear performance, but the best case is also linear performance. So that's something that I need to look into. Just something I found interesting as I was researching this. So if you have 300 gems and it takes four to six milliseconds to acquire any particular file, that can add up. So my ideas for performance improvements, essentially what I wanna do is I wanna take that ON search that we have and turn that into a constant time search. So searching the load path is ON. It's linear time because it depends on how many things are in that array. Well, my question is, okay, if that thing is linear time, what if we just stopped searching it? Let's just stop. Don't do it anymore. Just stop. You think, well, okay, how can we do that? The way that we can do that is that full path names don't actually search the load path. If the path name is totally canonicalized, it doesn't search it. It just goes and looks in the already loaded database. And if it's already in the, if it's in the already loaded database, it just says nope, done. If it isn't, it just goes and loads the file. There is no load path search there. So for example here, this top one does no search because it's a full path. The bottom one does do a search. So how can we do this? I mean, we don't wanna go through our applications and replace every single one of these things with the full paths. It doesn't make sense. It's too tedious and it's also gonna bind us. It's gonna couple our application to the location on the file system. We can't do that. So how do we do it? Well, if we look at canonicalization, the way that the canonicalization process happens, it happens in two places. Happens when Ruby searches the load path and it happens when Ruby gem searches gem specs. Now, for example, when you do require a foo, it's gonna look for a particular files. It's gonna look for foo.rbso.o and just the bare file name. And as a sidetrack here are some bugs that I found while I was doing some research. You can require .bundle files. This is just using pure Ruby, Ruby's require, not Ruby gem's is require. You'll notice that I'm doing a gem which I'm mutating the load path. So this shouldn't go through any of Ruby gem's is logic. I can require nocogiri.bundle and I can also require nocogiri.so. I'm on OS 10, which means that there's a .bundle file that exists, there is no SO file, but Ruby converts that SO into a .bundle. Now, if we try to do this same thing but we do it through Ruby gems, we actually get an exception. So fortunately, none of you have ever, ever run into this because that first require puts the Ruby, puts that gem on the load path. So you'll never run into this. Anyway, you're getting off track. Yes, I know, I need, all right. So back to this, we have different ways of canonicalization. Now, the way it works is we're taking that require parameter, we're looking at that, we're canonicalizing that and converting it into a file name. But given a particular file name, if we have a particular file name, we can actually predict what those short, valid short names are. We know that, we know the logic for this. We know that if we have libfoo, the possible things that we can pass to require are gonna be either foo.rb or just foo. We know that. So why don't we generate those in advance? We can keep a hash, we can say, okay, we have this hash here that if somebody says, if somebody passes me foo.rb, then we know that it goes to libfoo. If somebody passes me foo, we know that goes to libfoo. So today we have a process that looks like this where trying Ruby require, finding the gem with a file, all of these are linear time performance. What I wanna do is if we had this particular cache, we could say, okay, we'll change that, we'll do a hash lookup which is gonna be constant time and we'll be constant time throughout this entire process. Now, if it doesn't exist in the cache, then we'll go through and try Ruby's require which is going to be onetime. But since we're not mutating the load path anymore, that end time is gonna be shorter. So what I did was I put together a test that generated this cache and said, okay, I'm gonna activate many, many gems and study how the require time grows as we activate many gems. Now, this is what it looked like. Along the x-axis there is the number of gems that I have activated on my system. So I activated up to 1,000 gems and along there on the y-axis is the amount of time that it took and I want you to know that that's time in milliseconds and note that it is less than one, which is really awesome. And that it scales linearly too. I have 1,000 gems and it's still taking less than a millisecond to do this. Now, the way that I wanna do this is we could calculate all this cache on gem install. When you do a gem install, whatever, we can say, okay, let's calculate all the short names for the files that are inside that gem. So that's what I wanna do eventually. Now, the challenges with this are we have to be able to deal with dash i because you can do Ruby dash i, whatever and that dash i takes precedence over gems. But I think this is something that we can accomplish. We know where those are in the load path. We can manually search those. So we'll only punish people who are using dash i. Now, we have to deal with load path mutations. If people unshift onto the array, we have to be able to deal with that. But we know at boot time what that load path looks like. So we know, we can tell when somebody has mutated it. We have to deal with somebody doing this, unshift, whatever. So we'll punish people using dash i and we'll punish people using dot unshift. But if you don't do those, then it doesn't matter. So we also need to deal with bundler support as well and what I mean by this is, I want, if you look at the way bundler works, bundler essentially takes your lock file and just mutates the load path for you and gets rid of all of the gems on your system. I'm not saying it actually deletes all the gems on your system, it just makes them unavailable. So if you go and run inside of your application, if you say bundle exact Ruby dash i and you print out the load path, you'll see that all of your gems are already on the load path there for you, right? And what I'm proposing is that we don't mutate the load path anymore. So fundamentally, we would have to change bundler to go look up from this cache. So let's take, I guess, I'm over, but F it? I don't, let's do it straight. I want to talk about a strange bug that I encountered. So I was doing this, studying how auto load works and I was doing this constant reference here. I was doing, testing it out here. We had A and B and I was trying to come up with an example to show on the slides and I'm running it and like, okay, all right. I run this, same example we had earlier and it prints out A high and I get an error. And I'm like, wait a minute, this, okay. Can anyone spot the bug in my code? Anyone? No bugs? Anyone? No? No? No? Well, I should have put a troll face on here because there is no bug in this code. It's not there, it's not existing. So I kept getting this error and I'm like, okay. So I am a coworker of mine, I'm like, hey, I need you to try this example for me. Auto load just seems completely broken. I don't know how anything is working anywhere at all. So I say, I need you to do one file to auto loads another file and just do that. And he says, oh, it works for me. And I'm like, really? And he says, yeah, you must have broken something. And I'm like, well, no, what file names are you using? What file names? I said, try A and B. Make sure that they are named A.RB and B.RB. Oh, oh, they do break. So it finally broke for him, but only on certain filenames. So these filenames break. If you name a file in B.RB or RB, RB.RB. So the solution, let me tell you, the solution is don't name your files like that, you dummy. But I think coming from this experience, 99% of the time we blame ourselves, right? I mean, clearly, I showed this problem to my coworker. And he's like, oh, it's your fault. Clearly, it's your fault. It has to be your fault. I'm like, I'm not sure that it is. I don't think it is. I don't know what I'm doing. I actually showed this problem to three people total and all of them told me it was my fault for us until I got them to change the filenames. And the thing is, I think that we blame ourselves 99% of the time we blame ourselves because 99% of the time it is our fault. We did make a mistake. We did make a mistake. We did do that. Now, oh, I have to, I have to show you one other thing. I'm sorry, this auto load thing. I actually had two of my coworkers help me with it. I had Eileen help me with it and we were debugging through the C code and she starts reading the documentation and she says, you know what? This example is in the documentation. It's literally written there. It doesn't work. Anyway, okay, okay, okay, okay. Okay, fast forward. Come on, come on, built. Yes, yes, yes. Okay, right. So what I wanna say is that we should take the time to understand why these particular things break. Like even if it is your fault, you should try to debug it because I think probably people have run into this exact issue before and then they just rename the files and they're like, oh, I probably just did something stupid. I don't know, right? And then moved on. We've all done that. I've done that. I know that you've done that too. You're like, oh, I just changed this thing, now it works. I'll move on. But we should always be asking why and taking the time to find out. Then we should just make it fast. Just do it. Please make it fast. Thing is though, I said earlier, I mentioned this earlier, please make it fast. Nobody will do it, but nobody will do this, right? Like nobody will do this. My complaint IRB is slow. They say, oh, just uninstall gems. And I'm like, no, I just want it to be fast. Why do I have to uninstall gems? Like, ah, just uninstall your gems. Thing is nobody will do this stuff. I have to do this stuff. I have to do it. I want it to be fast. I have to do it. But it's not just me. You have to do it too. We all have to do this stuff. If we don't do it, nobody else will and it's just gonna be the same forever and ever. And we can't have that. We need to change it. So with that, I'm gonna end it. I'm over time. Thank you very much.