BathRuby 2016 - How are Method Calls Formed? by Aaron Patterson

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so last time Phil was my MC I lost my passport in Belgium I don't blame him but I'd I do it was his fault no I'm just kidding okay so we're gonna make ho this is sorry this slide is for a different audience so we're gonna do it I want to do a little bit of a conference recap here I want to recap all the presentations that we've seen so far today so I'm going to do that very quickly Xavier talked with music to my ears Coraline talked about in u4j was great I did not know that they use Java in the matrix I really enjoyed Courtney's talk about open source for my benefit apparently I've been doing open source wrong Janet's talk was great but she gave me the impossible task of coming up with a pun about gender inequality but I think I did it cognitive bias you think and finally Zack Zack if you don't if you don't like these fun puns you can feel free to Briggs it so I was really honored to be invited to Bath read me I don't really know anything about Rocky so instead I'm gonna talk about programming and I guess some of my some of my slides do have color in it but we'll see how we can fair whoo it's pretty we're gonna talk about how methods are formed today I want to talk I my alternative title to this was method mania but first I want to introduce myself a little bit I work for a company called Red Hat I don't know if you've heard of them they're a small startup company in the United States specifically I am on the manage IQ team and our team at Red Hat manages cloud stuff so if you have cloud stuff we can manage your cloud stuff and actually our app our application is open source and you can look at the source on github it's just manage IQ on github but I'm gonna talk about that a little bit more later also we're hiring ok so my name is Aaron Patterson and I have come from the United States to bring you freedom and I have to tell you I have to tell you I I gave a talk in Moscow and used this exact same joke and it went over like extremely well I'm still alive anyway like so I like to pay attention to the news and stuff and I have noticed that with the recent political climate here in Europe I think this might actually be better I'm from the European Union bring you freedom that was supposed to be a Belgian waffle but as an American I have no idea what those look like anyway I'm really honored to be here I'm excited to be in England because it's really cool because everybody here has an accent it's also it's also neat like it's kind of a novelty I'm in a foreign country and everybody speaks my language so that's that's really really cool I also feel like I'm in some sort of like a romantic comedy where I've been whisked away to England and everything's like anyway another reason I like coming to England is because I don't feel so bad using freedom units here for those of you that don't know that's like so you might refer to something that's 10 degrees C where we would say F work and we don't use C in the United States because C stands for communism and F stands for freedom so like that's that's what we do there anyway so yeah I don't feel so bad using those units here but anyway I'm very very honored to be here like truly I'm so happy to be here and in bath the city is very beautiful and I really do love coming to England and I tried to come up with an emoji for this conference and this is the best I could come up with is so there's nothing that's bath and then route B so bath through B now many of you might not know this but there was actually a very important computer science discovery made here in fact you might not notice this but I was researching this on Wikipedia that I put in there yeah one of the most famous sorting algorithms was invented here at Bath College by a man named Alexander Graham bubble now now bubble he invented the bubble sort and just so you're putting this together the bubble sort was actually invented in back get it yeah how much time do we also another weird thing I noticed that my hotel room only has a shower so I thought that was kind of weird like I thought you're supposed to have a bath there the other things so so I to get here I took a train and I just feel like the entire country of England is playing a joke on me because I went to I went to purchase a ticket to get here and I go to the window and I'm like yes I need a ticket to Bath Spa is that a valid place name and they're like yes your ticket to Bath Spa they they didn't think it was so weird at all but to me it was just very weird also last night we were talking about how there's two different pronunciations bath and bah I think and there people are going on yeah you that's the right way to say it and I could not tell the difference I'm like you're both saying the same thing it doesn't make sense what is going anyway so I love cats this is my cat one of them that's feet at sea-tac airport Facebook YouTube is her name that's her full that's her legal name we just call her tutu for short and then this is this is also her she likes to sit on my desk so I think that's very cute but also in the way a lot and then this is the other one he's a bit more famous this is Gorbachev Gorbachev puff-puff thunderhorse that's his his full name I actually wanted so when my wife and I got married I wanted to change our last names the thunderhorse think it's awesome but she would not have it so that's too bad anyway my wife really wanted me to give a TED talk someday so she made this slide for me but I don't know anybody named Ted so I can't do that also I enjoy hugs and today is Friday so I would like you I'm not gonna make you all do it now but tonight after this after this shindig that's gonna marry that's so American immunity please come give me a hug for being on Friday so today we're gonna talk about methods let's let's talk about methods we're going to talk about methods and we're going to talk about method optimizations so I want to talk about methods I want to talk about types of methods how methods work and also bytecode for methods and some VM internals and then after we talked about how methods work I want to talk about method optimizations so I want to talk about inline caches polymorphic methods polymorphic inline caches and then optimization tests and I want like I hope you're all very very proud of me because you you may have noticed that I localized here for all of you anyway so I'm trying to lay I'm trying to lay all this out for you so that you know that there is a method to my madness more localization right there so this is this is a highly technical presentation this is a very technical presentation that's how we're gonna end the day today but I actually want to start out on a not so not so technical note at the beginning I usually give very technical presentations but I want to start out with a little bit kind of some serious stuff that is not so technical I want to give some I want to give some advice for new people in the audience people who are new to programming are new to the industry and also those of you who have been here for a while I want you to kind of listen in to I want to talk a little bit about accessibility this presentation I'm giving I'm gonna be talking about some very complicated topics but I'm gonna be building on easy stuff at the very beginning and then getting into more difficult stuff as we go on but my goal with this presentation is to ensure that everybody in the audience can get something out of this out of this talk so yes there will be very difficult concepts but we're gonna introduce those slowly so hopefully some of the new developers here can get that get that information as well but the thing I want to pass on to you especially to new people is don't be embarrassed about asking questions about this stuff some of this stuff is very difficult and I remember a time a point in my career where I was afraid to ask questions of my colleagues because I thought hey if I ask this question am I gonna look stupid and I think it's very important for especially for new people to not feel that way I want to foster an environment where you feel okay to ask questions about anything that's being shown or anything in the industry because all of us had to learn that stuff at some point so you shouldn't feel embarrassed if you don't know something the other thing I want you to do is to when you're asking these questions be genuine about what you ask be genuine about trying to learn something and the thing is if you ask questions of somebody and you're generally trying to learn and they do think you're stupid for doing that you probably don't want to work with those people so just pro tip for all of you so if you notice that somebody really doesn't understand something and they're being genuine about it please help them out try to make them feel comfortable so we're going to talk about some very high-level stuff we're gonna go from high level concepts down into low level concepts and we're going to be sort of oscillating between those two so we'll talk about high level stuff with methods and we're going to dig down deeper and then come back up for air once in a while so let's get started with this I'm excited to give this presentation because this is the first time I've been able to talk about this this stuff that I've been working on but I also want you to forgive me a little bit because this is the first time I've given this presentation so it might be a little bit rough around the edges but the first thing I want you to know is that like TL DR if you don't want to watch the rest of this presentation I failed that's basically it I failed so we'll we'll talk about that we'll talk about that failure at the end and you'll understand how I was a failure but I want to talk about the stuff I learned along the way so the first thing that we're gonna cover is call sites call sites and programs we're gonna define what those are call sites are pretty easy to identify in your code they look that looks something like this this is a call site anywhere where you see a dot that's gonna be a call site that's a call site and I'm gonna refer to some things like on the left here I'm gonna refer to that as the left-hand side yeah the left-hand side that calls our call site and that other side I'm gonna refer to that as the right-hand side ah yeah highly technical so an important thing about this is that call sites call sites are unique so if I had that hello world listed five times we'd have five different call sites there I want to show you some more examples of them in in Ruby these are this is a short code example here so we have a call site up here we just saw that one we actually have another one right here where we're saying object new our left hand side is object class or right-hand side is new we've got another one here that's we've got an implicit left hand side that's our self so it's actually self dot foo it's just that we can omit the self dot and in the case of this it's actually main so if you write a quick Ruby scripting you just do like put self from that script you'll see that it says main so that's our main object we've got a few more as well yes so left good job Aaron left hand side self Thanks oh I gotta remember I'm trying to break I'm trying to break the the sixth wall are we doing six well I'm breaking six walls hello viewers at home should I say hi mom all right so more call sites we got some more code down here at the bottom there's one more here when object that's that is one that's equivalent to doing object object triple equals x so you could write that one out as the same thing as an object triple equals x but we're gonna have some more notes about that that's an interesting one and of course we got another one here the double equals that's a method call we've got another one here an implicit call to self and the point I'm trying to make here is that we actually have tons of these they're all over our code they're everywhere they're everywhere you might not necessarily notice them but they're definitely there they're there throughout our code base all right so let's do a little quick review of how Ruby's virtual machine works so the way Ruby's VM works is it's a stack based virtual machine and what that means is we have a list of instructions and we have a stack that the virtual machine works on it takes those instructions and uses that stack to work out values so for example we have this tiny little program here the way that this works is we've got our bike code it's just a list here on the left and on the right is our stack that we work with so what this bytecode does is it says ok we're going to push 6 onto the stack 6 shows up on the stack then we push 8 so now 6 & 8 are both on the stack and then we say add and what that does is that pops both of those values off adds them together and then pushes the value back onto the stack so that's how stack based machine works and that's how mris virtual machine works and what's interesting about the bytecode stored or the MRI uses is that it's actually just stored as an array so if you look internally the bytecode looks something like this if I were to write it in Ruby it would look like this it's it's just an array and it's got some more arrays inside that array so it's basically a two dimensional array so we have on the left of that we have the operator what we call the operator and then we have an operand so we're saying push six what's what I really want to drive home with this is that this bytecode is actually allocated and towards somewhere it's it's there it is a physical thing well okay maybe not physical it is a thing that is inside our computer stored in memory and it's actually there and we can manipulate it okay so the next thing I want to talk about is how methods work methods from a high level what they need to do is they need to find the name of the method we have to do is find the name of the method then we find the type on the left hand side then given the name that we found given the name that we have we have to go execute that method we look up that method off of that object and go execute that code so from a low level a very low level we're going to look at the byte code for how that actually happens we'll take this example here we had foo and bar and working we're going to get the instruction sequence for that and this is the Ruby code or they this is specific to MRI for getting the byte code for that particular method so if we print that out it's going to look something like this which is kind of difficult to read but really the things we want to focus on are these two lines here where it says four and six this get local that is that is our method call so if we have two method calls let's say we do that twice and we look at the byte code from that we'll see again we have those two those two lines repeated twice so we can actually map this byte code back to our original code if we look at the original code we have there that's the original method you can see that this get local OP zero - what that is doing is it's getting the left-hand side to this bar and it's pushing that onto the stack and then the next operation is actually calling the method we're going to call the method bass on bar so that is what this opt send with how block instruction does so here again we have our operator in our operand and then we have this again we have that down here with the ops end without block and then we have a third argument there right there that we're gonna cover a bit more in depth ly later but this is what this is what the actual byte code looks like for executing a method so if we were to look at the stack on this when this is executing we'll say okay we're going to call take this transitions there we go bar we're gonna push that bar on to the stack the VM is then going to execute the method against a bar it's a little pop go come on computer it'll pop bar off the stack call bars on it get the return value from that and then push the return value back onto the stack transitions yes so that's how it does it now what I want to look at is we're gonna go a little bit deeper here we're gonna look at this op send without block we're gonna look at the C code for that and the place you can find that in MRI is in this file called insn SDF so if you ever dump that bytecode and you look at those symbols on the left hand side each of those map to a section inside of this file inside of this insn SDF so if you're curious about what ops end without block is just open up that file go search for it and then the code will be right there in the block so this is this is our op code and you can see down here this it's very short that's the entire thing we'll say okay we're going to search for the method once we've found the method off of that object we're gonna call the method that's it that's all there is to it yeah so let's talk about finding the method because we kind of left this out in order to find the method we are in order to call the method we have to be able to find it right and that method is defined somewhere so how do we find that method oh I'm gonna cover the algorithm for doing that and I kind of rewrote it here in rubies so that would be a little bit easier to understand the C code is not as fun to read so essentially what we're doing is we say hey class tell me what methods you have get the class for the object say what methods do you have if it doesn't have those methods it goes and looks at the superclass and if that one doesn't have it it just repeats the process so we keep going up the tree so for example if we had a structure that looked like this and we're calling a dot new dot foo in order to find that foo method the way that it looks is we have this inheritance hierarchy and we say hey do you have who I'm going to look at the method table nope you don't all right I'm gonna go up and look at class B check it it's method table nope No no food there now let's go check the method table on C nope not there then finally go up to D yes it's there we've found the method we can call it hooray so we could say that looking up this method is an Oh an operation where the number the that n is the size of our stack or that in is the number of classes that we're inheriting from so in other words we could say the more ancestors there are the slower the method call is I'm kind of lying but well look at it hold on so let's test to test this theory let's test method speed if I what I'm saying it whoa Wow it's less pink can we put it back it's kidding I'm just kidding so if what I'm saying is true we should be able to take a class that has ten ancestors and another one that has 10,000 ancestors and compare the two and see that one is slower than the other so I did exactly that we have this we have this class this code here that creates one class that has ten ancestors and another class that has 10,000 ancestors and it's not actually 10,000 there's a little bit more in there because of the built-in classes so if I run that run this code and for those of you that don't know this is a in IPS benchmark or iterations per second what this means is the higher the number is the better it is the faster it can run because we're doing iterations per second so the higher that number is the faster it is excuse me oh yeah a transition so when we run this code and you look at the output here's the output right here and the numbers are almost exactly the same so it actually doesn't matter whether the there are 10 or 10,000 ancestors so how is it possible we looked at the we looked at the algorithm for finding this method how is it how is it possible that they're the same speed so what I want to talk about next is speeding up method calls the way that we do that the way that we can speed up a method call is essentially we cache things that never change so if something never changes we don't need to compute it over and over again we can just cash it if we take a look at this benchmark if you look at this code you'll notice that the very Abell 10 its ancestors never change same thing with 10,000 its ancestors never change since the ancestors never changed we can just do that method lookup once so we do that method lookup once we cache it and then we can just reuse that cache over and over again so the question is I know many of you are probably using a cache at work and your applications and you're like I'm gonna use an in-memory cache or I'm gonna use a redis cache or whatever you know memcache cache but you have an idea of where this cache is it has to be somewhere has to live somewhere so where do these caches live these caches actually live inside the bytecode itself and that's what I was pointing out in the third argument there that value right there called call cache that is the cache that is where these method lookups are cached and it's per call site now since this cache is stored in line with the byte code it is in line in the byte code we can call this an inline cache okay so we have what is called an inline cache here now what I want to talk about next is something that's slightly pertinent to your applications and now I'm nervous of this it's going to fall on me it is improbable that I will die on stage but not impossible apparently yeah so if somebody ever says to you breaking method cache this code breaks method cache what this is what I want to talk about this is what this is what they're talking about if we look at these call sites so we have these all these call sites here these are our this is our code that we're looking at earlier we've got all these call sites all of these call sites contain a cache write that call cache the one that we pointed out in the byte code they're all stored there although this one that is now pink is different and we're going to talk about that one now because I think it's very interesting well I think it's very interesting we're going to take a slight detour here so we talked earlier about how when when you do when it's the same thing as object triple equals something else let's take a very slight detour about case when versus an if statement if we look at this code here these two bits of code on the left should be equivalent right when we read that they do the same thing so it's the same they do the same thing just one is an if-elsif and the other one is a case one so what's interesting is if we benchmark the two of these if you if you benchmark close to you'll notice here that the case when version is actually slower than the if-else version which i think is very very interesting even though the two do exactly the same and the reason is because this one on the left here where we actually have that triple equals written out we have a call site there that has a cache where this one this case when statement doesn't have a cache there's no cache there and you can actually see this if you do if we take a look at the bytecode if we pull up the bytecode for both of those things you'll see that they look almost exactly the same so at the top we're doing a get in line cache that has nothing to do confusingly has nothing to do with this in line cache that we're talking about today ignore ignore that it says cache just ignore that that call cache there on the left that I'm pointing out isn't isn't here on the bottom so this this check match is essentially the same thing as that triple equals call though one one doesn't have a an inline cache and the other one does and what I'm trying to say to you is don't go changing all of your case case once game it's a triple equals please don't do that we should we should basically just fix this it's just something we need to fix so I'm working on a thing to essentially just drop a cache into the bytecode there but I did think it was very interesting when we're looking at this that those case when statements don't actually expand to the triple equals they have their own special byte code and that byte code doesn't have a cache so again we have our we have all these caches here and what I think is neat well maybe it's not that need it's important to doing optimizations for this code is that these caches are everywhere so the size of this cache matters if we increase the size of the cache then it's going to increase the size of our entire program possibly so we need to be we're gonna talk about that a little bit later but we have to be mindful of that all right so we're talking about caches and we found the location of the cache and we know what it does but what do we actually store in that cache what is actually store it in there what's stored in the cache is just a key in a value the key is a serial number for the class each class is a serial number and the value is the actual method itself okay so we're going to talk about that serial that serial number and the value of the cache now this is when we're talking about breaking method caches what we're talking about is changing that serial number we all know this when we get Cass MIT cash from its misses in production we're we've got the wrong key so how can we get that key to change I want to talk about different things that make that key change in rubies so how how to make cache misses so the first thing we need to do is actually know how to tell whether or not there's a cache miss and we can do that with Ruby vmstat if you run this code you'll get these you'll get these values out these are three important values but they are implementation specific if that top number changes the global method state what you want to look for is changes in these numbers if that top method change or that top number changes that's very bad it's V bad I'm trying to like shorten words that don't need to be shortened because I noticed that that's what young people do today so V bad so if that top number changes that's a very bad and the reason it's very bad is because that one impacts every single cache throughout your throughout your code now we talked about how all those caches are in all those different places when the cache key changes it only impacts it only impacts those particular call sites so if you have a call site that's associated with Class A if you modify Class A it'll only impact those call sites now the global state that one impacts all of those so if you change that number it's extremely bad this number if this number changes it's not great but we want to avoid changing it if possible now in the course of putting together this presentation I was like well I'd really like to chit I really like to show examples of these numbers changing so that people can actually see it so I decided to throw it in an r-spec test and if you printout vmstat in all these if you look at the output from this from this program you'll actually see that every test changes the class serial number so you'll see that increasing through it through each of the each of your it statements so basically our spec is breaking method caches when each of these each of these its statements ran so I actually filed a bug about this and we're working on trying to figure out a way to fix that so don't like don't go switching off our screw our spec these numbers are increasing it's terrible it's fine don't worry about it anyway so how do we how do we actually break this cache we can do it when we whenever we define new modules you'll see that class serial number change whenever you define a new class you'll see that class number change whenever you reopen a class you'll see that serial number change so if we're doing that stuff at runtime it's bad but if we do it at boot time like we're doing here it's fine we're doing that once and when we're done with that it's going to be stable throughout the lifetime of your process which is what you want you want those numbers to stay stable it's fine if they change but as long as they're stable that's what we really want so any of these misses at boot time is amortized across the life of your process so it doesn't matter at boot so let's look at some runtime breakage right to do look to look at runtime breakage I wrote this little tiny little method up here at the top so you can say like hey let's take a look at you know print out the differences and you'll see here this one on the left there's no change there everything is fine with that one on the right if we extend an instance if we do an infant's exec or if we call dot singleton class those will break your method caches and actually those two those two on the bottom now those are the ones that are spec is doing and the way that these are breaking the cache is that they're accessing that object singleton class so if you have an instance of foo that object type isn't just foo because you might have monkey patched it if you monkey patched it those monkey patches go on to the fig the singleton class so actually it's classes the singleton class so anytime we do a class singleton class act access will break cache for that particular instance so we can we can do a mapping here we can say okay let's take a look at what a is class is what its so-called real classes versus B's class and I made a little diagram there you can see B's since we're not monkey patching it or anything since we're not accessing its singleton its real classes foo it is really just a foo since we're doing an instance exec on a and we're adding a method to it that method needs to go somewhere that method goes onto the singleton class of that instance so that class is that instances class its real class is that that singleton class on the right so when you say class of some instance we're going to get a singleton in the top case and we're going to get a normal class on the bot in the bottom case so cache misses occur usually at boot or when whenever we have new classes or modules at boot time or when we're accessing singleton singleton objects as long as the is long like I said as long as this number doesn't change while you're running your application you should be fine we've actually done tests in this in rails to ensure that this number doesn't increase as we're pushing requests through the application but if you ever do any test we don't we don't have any regression test for this it's a problem so if you ever see this actually happening in rails it shouldn't be happening so let us know so the next thing I want to talk about for cache misses is the the stuff that we looked at earlier is is kind of questionable if you think about it normally defining those classes is fine defining classes and modules is fine but as soon as we start accessing singleton classes it's getting a little bit questionable when you see that instance exec I expect your eyebrow to go hmm what's going on here well if we're doing and if we're doing it include or in extend against an instance that might raise your eyebrow but what I want to talk about is cache misses when we're doing something perfectly normal and that's in the case of polymorphism so let's say what I have right here is a test that does it's a polymorphic test polymorphic call test on the left here we have an instance of a and we're calling a method on a 12 million times and on the right we have B or a and B they're both going into foo and we're calling it we're also calling 12 million methods on that side to you'll notice that I'm increasing I by two because I want the total number of method calls to be 12 million so I want to measure how long it takes them to make 12 million method calls right here in food that's the important one that one so when we run this you'll see that the left hand side is faster than the right hand side we were able to run this run this one more quickly this polymorphic test is actually much slower and the reason is because if we take a look at that call site inside of foo will know remember that the key is class of bar serial number and if we think about the values that are running through that method it's going to be a table serial number B dot serial number a table serial number B dot serial number so we're oscillating between those two on every method call which means that that cash is never hit we never hit that cash it's always something different so this call site here inside of foo it always sees a it only sees the class a on the right hand side it sees a and B it sees those two so when we have a call fight that sees one type we can call that site that call site monomorphic that is the name of it so now if you when you go back to work on Monday you can say oh these ruby has monomorphic monomorphic inline caches call sites with two or more we can call those polymorphic and if they have too many there's another another term if there's just too many and we'll talk about too many later it's mega morphing so let's call too many what is too many we'll talk about too many so how do we make this faster so today the cache the cache code looks something like this we say okay look up the class compare the serial number on the class to the serial number in the cache if that if they're the same then we to hit otherwise we need to we need to get the serial number find the method and set all those set that in the cache so today what we have is a monomorphic inline cache we call it a monomorphic inline cache we'll break this down a little bit it's in line because it's inside the byte code it's a cache because it's a cache alright it's monomorphic because it only stores one value so what I would like is to have a future cache that looks something like this where we say hey you know what instead of just storing one value let's have an array of values where we can store maybe two or three two or three different values right we'll say like I'll you know if we just have a short little cash in there maybe we can look up two or three values so when we're talking about too many we're saying well how long does that array have to get before it doesn't pay off right clearly we don't want to cache like thousands of entries it'd just be too slow to search through thousands of entries so when we say too many that's what we're talking about so what I would like to have is a case where we say okay let's let's store like two or three values and see what that see what that looks like now this type of caching when we're storing multiple is called a polymorphic inline cache because it's we're storing multiple values in line and it's a cache we did it so I wrote a patch to do that this is now we're winding into the failure part of the presentation and so I wrote a patch for that and this is it and I want you to read it very closely because there's gonna be a test on it later and that is the whole thing okay get it good perfect it's actually a 186 line diff which actually isn't too bad 186 lines and that's the entire dish so it wasn't even 186 lines of change if we run this polymorphism test again these two these two tests if we run those again actually the numbers are almost identical all right mission accomplished alright actually let's do that one more time this is such a I'm so proud of this transition I weren't like I was up really late last night during this one alright so okay we did this it's great great we're cashing this stuff these benchmarks look amazing polymorphic calls are exactly the same speed as monomorphic calls everything's great but we need to do one more important thing and that is we need to measure measure impact we need to measure real-world impact unfortunately this is where things suck nobody wants to miss your real-world impact it's terrible so one performance tip to all of you is to only speed up bottleneck please make sure it's a bottleneck before you need to speed it up so all right how do we determine whether or not we're having a bottleneck with polymorphic call sites so that's what that's really what this optimization is doing is speeding up polymorphic call side so the question is what percentage of call sites are actually polymorphic and how can we measure that so in order to answer this question what I did is I added logging to that cache key look up a place where we were talking about that cache hit or miss I added logging to that particular section of code here it is again yet another test actually that's the entire diff it was actually really easy that's the whole thing so what this allows us to do is it gives us a trace point inside of Ruby where we can log each inline cache hit or miss so what I did is I wrote this this is an example of it we can say all right let's have a new trace point and we care about we we want to print out the Col site info that's information about the particular call site and we care about hits and misses we want we want to know about both of those so any time there's an inline cache or a miss this block will execute so then what we do is we enable that down here at the bottom so if we read this carefully we'll see that after this trace point is turned on we actually have one two call sites so a dot foo is a call site and then bar dot Baz is another call site after we enable logging so when we run program that's what we'll see we actually see two two logs output and the values over there are the call site ID so it's a unique ID for that particular call site then a serial number and that's the serial number of the class that's our cache key and then finally just the class itself for me to read because I do not know what these numbers mean reading a number of makes doesn't really help out humans very much so let's take a look at more calls this is a this is a sample program we got a ton of stuff over here and I've broken it down into the stuff we care about on the right so in this particular case we've got a polymorphic method we've got two or a polymorphic call site we're passing instances of a and B through foo and recording all those values and if we if we sum all those values together we get an idea of what it looks like we say we see that we have a total of four call sites and three of those call sites see one type so three of those call sites are monomorphic and one of those call sites these two types so one of the call sites is polymorphic so now we have a framework for understanding what percentage of our calls are polymorphic versus monomorphic right so what I did is I ran this against our application at work this is our app you can go to this go to this page on github to see our application we we have one of the largest open source rails apps I've seen and I also wanted to run it against this because this is where I'm at work I work it's my job so I should probably do that so I ran this against our application and put our rails app into production mode and ran about sixteen hundred requests through it and this logged nearly four million calls so there's four million data points for us to look at and the bad news is okay here is the graph that shows the bad news this is the very bad news this is the graph alright so on the very left there that is our that is the number of monomorphic calls okay that one next to it right here that's to the - calls polymorphic calls so what this optimization did is it optimized that so a very out of millions and millions of method calls only that percentage would be optimized by this optimization so what we're saying here with this graph is that most call sites are monomorphic they have 1 1 type passes through them in our application so some interesting stuff I learn from this let's break this down a little bit more so here is our original here's our original graph we had some call sites that had 16 hundred types passed through them one call site had 1600 different types so what was that I tracked that down and what it was is it actually the source was a vent machine we use at the time we were using thin as our webserver thin uses a vent machine and if you look inside of a vent machine every time it allocates and allocates a connection this is actually I think connection dot new you'll see inside of it says allocate dot instance eval oops we're accessing a singleton class there so it's breaking the cache for that particular instance so every every one of those none of those event machine connections will ever hit cache so that's that's where those 1,600 that 1,600 came from I did about 1,600 requests event machine made about 1,600 connections so boom there you go all right so let's look at call sites where the number of types is 2 so that might be interesting this is a graph or a number of call sites is 2 and I just want you to look at the shape of the graph so what's interesting is we have on the left here one that was heavily used and has two types and I know you can't read it I had to do this in are apparently are is the only thing that can handle this type of graph here's a subset of the values I want to zoom in a little bit here on these top these top values the very top value is 2 we're gonna look at those two types it's either a set or an array that was the very top value and I found the place in the code that does that and it was saying hey does this dot include and what it was is mime types we were saying hey the this list of mime types include something for some so for some reason this list of mime types was either a set or an array why why was it an array sometimes I have no idea but maybe we want to just turn that into a set and now this is no longer polymorphic the next thing was array and nil and it was saying like hey is this thing blank why is it nil sometimes I don't know the next thing was symbol and string and we were just calling to ass on calling to s on those and what I think what I think is interesting about these two different types is that these these call sites with these types didn't seem to be what is the word I'm looking for intentional it didn't seem like they were intentional so there's an array sometimes but it could have been a set there was no reason for it to be an array and if it was nil in those cases it could have been an array there was no reason for us to make sure it was an empty array string and symbol and straighting this one's a little bit more difficult though so very few of these very few of these call sites were actually for performance or behavior related reasons so for example I want to give you a little bit of an example of performance related polymorphism so for example let's say you have this instance foo and you have a predicate method on there that's who is thing equals equals interesting so every time you ask foo ru interesting it has to do that comparison right one technique we can do is if we have to call interesting many times what we can do is calculate that on allocation on creation of the object and we just allocate a subclass that we know is interesting and we'll just return foo every single time so this is an example of how we can use polymorphism to remove runtime conditionals from our code the other thing the other example might be behavior so let's say we want to configure some object with particular behaviors so we'll configure it with different types depending on the behavior that we want it was very rare for me to see this type of polymorphism this type of polymorphism that I would say is intentional polymorphism are very rarely saw that all right so it is time for me to STFU so it's only a failure if you learn nothing I think and even though we have failed to this this polymorphic England cache isn't going to help out our application at least I learned something along the way and I hope that I can relay to the rest of you the things that I learned the other thing that's interesting about this is that a polymorphic in line cache is load specific so it wouldn't help out our application but it may help out your application I don't necessarily know that our code is representative of the of code at large right it could be that our code is a total outlier and we don't know it could be that all of you out here are using this really important polymorphism stuff and this would help you out tons and I just don't know it so what I would ask you is if you have time go check out my Fork of Ruby check out these two branches on github the top one the pick one that has the inline cache so you can test out your code and then that icy measure one that has the the logging code in it if you combine the two then you can get those two at the same time the other thing is only optimized code that matters of course Polly I should drive this home only optimized code that matters I am NOT going to push for this code upstream because it doesn't really matter and take measurements please take measurements if you don't if you're doing optimizations without taking measurements how do you know that the optimization worked you'd be surprised how many people do this without measuring and also finally use more polymorphism please make my work worthwhile so thank you very much you
Info
Channel: Confreaks
Views: 6,255
Rating: undefined out of 5
Keywords:
Id: b77V0rkr5rk
Channel Id: undefined
Length: 48min 30sec (2910 seconds)
Published: Fri Mar 25 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.