 Hello, everyone. My name is Kat. Thanks for coming to my talk. I'm a software developer. I've been doing PHP for give or take seven years. I also read like Golang. I do a bit of Java at work. I do a tiny bit of Ruby, a little bit of Bash. So I kind of do a bit of everything. And I quite like to pick and mix. I work for a company called Brightprol, and they're in Bristol. I've lived in Bristol for the last almost a decade now. I'm from Warsaw in Poland originally, so hence my slightly different Twitter handle. And today I want to talk to you about optimizing performance. As developers, when we start working on something and when we're given a task, then we usually probably like to go off and do a bit of planning. So you either sit down and you work out what you want to do, or maybe you have a proper planning, like agile style with the sticky notes on the whiteboard. And then you go and write your code, and then you run your tests and your tests pass. And maybe your continuous integration pipeline passes as well. So we're good to go to production, right? Well, where is performance? Most of us don't really consider performance day to day when we're working on stuff. And that's a very typical scenario. And you can't really blame developers for that because analyzing performance is hard. It can be tedious. And not many people know how to get started with it. I mean, we all know the theory of what is it about, but how do you actually go about it? How do you actually do it? So today, I thought I'll show you a little example of how you can go about it. I'm by no means an expert, but this is just me sharing what I've learned when I first had to do it for work. So let's start with the first things first. Why should you care about performance? I'm not going to spend too much time on this because hopefully it's obvious to most people. In case it's not obvious, how about this? Fast is better than slow, duh. Memory efficient is good. Saving money is good because you may run your app on less servers if they need less memory. Running out of memory in production is bad. And suddenly running out of memory in production is really bad. I mean, probably some of you have been there. I've been there. Or maybe you're just forced to look at performance once. Maybe one of your graphs starts looking like one of these. So the top one, it shows memory. That's a typical memory leak up there. So if you have the jaded line at the top and that's the GC doing its work, you're still garbage collecting, but overall, over time, that line keeps going up and it just creeps up over time. Or maybe you had one of those graphs at the bottom with some spikes in here, so some requests taking 30 seconds or longer and you're wondering why and what's going on in here. Or maybe you had the dreaded fatal error, a loud memory size exhausted. Or maybe you just want to have a look inside the memory and just figure out what's inside your code when you're running it. So that kind of covers the whys. We all have to deal with it at some point in our lives. In terms of when you should look at performance optimization, you should kind of be looking at it every step of the way, like every stage of the software development lifecycle, really. So you should be looking at it in dev when you're writing code or maybe you're asked to analyze an existing app. You might have a dedicated load testing phase before you go to production. So after you've done your unit test and your general testing, you might have some kind of performance testing suite that will run and then see if you've improved or maybe degraded performance. And then even after you're done working on your software, you should still keep an eye on it over time because sometimes issues only come out a bit later because maybe the memory leak is quite small, so you only notice it after some time. So you should monitor your production environment to spot any issues and then you can just tackle them right away. So that covers the when and the why. What about the what to optimize? I mean, we all kind of have a general idea. When I look at problems, I like to start with this question. Do I really need to do this? And that's something that it's so easy to forget because we're all just like jumping in and just start micro-optimizing right away. And it is really easy to forget that it is important to take the step back and first of all, ask yourself, is the thing that I'm looking at, do I actually need to do it? Like is this redundant code? Maybe it's dead code. Maybe I don't need to do it more than once because of course doing it faster is better but not doing anything in the first place is the best kind of optimization you can make. So think about that first. And that applies to, like I said, dead code, maybe something that you're doing multiple times instead of once. So kind of be lazy to start with. If you don't need to optimize, if you can just get rid of something and do that, in terms of the general things, most of us have a good idea for what we should look for. So we want our programs to run faster and consume less memory. So we want our garbage collection pauses short and infrequent and very efficient. So we know that we should probably measure memory usage and execution time. In addition to that, you should probably try and establish your maximum, minimum, and average values because it is important to know what your app's requirements are. And your CIS ads and your DevOps will love you for that information. And very often, developers just can't answer that question when they're asked, like, you know, what kind of server does your app need? You know, we just like to chuck stuff over and go, well, can you just deploy that now? And then if you can actually give your CIS ads or your DevOps some kind of information for like, I'm expecting this app to require this amount of memory on average, they'll love you for that. And then again, you know then what to watch out for when you're monitoring your production. You know what's normal and what's maybe slightly off. So this is my very, there's a bit of caveat to this and this is my very unscientific graph to try and explain that. So we have three aspects. We have memory, memory, time, and code. And we're all kind of aiming for that zero point here, that unicorn, where we've got as little memory as we can, it's just the code that we need and as little times as possible. But it is important that that's, it's important to keep in mind that that zero unicorn point is, it's not really something that you should necessarily aim towards. So optimizing performance is more of a balancing act because it does come at a cost and the cost is readability of your code and maintaining ability. People sometimes just go crazy and they will write really weird code just because in theory it performs faster but it makes no sense to anybody else and even them reading that code three months later they're like, I don't know what I meant here. So it is important to keep in mind that you do sometimes compromise readability if you want to do things faster. So you shouldn't necessarily aim for that zero point, like a die-hard kind of thing. You should just stop at a sensible point. Something that is good enough for production is probably good to stop at. So don't necessarily expect to get to the unicorn. In terms of, again, the what, there are a few sort of typical things that we probably already know when we think about performance. We've got the memory leaks, which is a potential issue. So the amount of memory your program uses may be increasing over time or maybe it is that your app is just using more memory and it could use a bit less and you just want to make it use less memory. And that might be caused by leftover open file handles, your overflowing buffers, that kind of thing. Timeouts, so your app might just be stuck waiting on something and you don't know why or what it's waiting for. Concurrency, if your program is concurrent, it's not something that you typically do in PHP, but you can. So if your PHP is concurrent and you've got all the lovely issues that come with concurrency, like race conditions and threat safety and dead logs and live logs, so that's something to watch out for. The normal redundancy and inefficiency, so doing something more than once and also dead code and unused libraries. And you might think, well, that's not really a big issue, but it will affect, for example, your deployment speed, your build step time, your compilation speed, because if you're just pulling some kind of libraries that you don't really use, then you're still taking that time to do it and then you're not using it. So even stuff like build time might matter to you. And there's obviously a lot more issues around your server config, where you run your app, how you split it, but I'm not gonna focus on that today. So you could do things like opcache settings, optimizations, you could tweak your Nginx and Apache settings and that kind of thing, but I just wanted to focus today on specifically the code issues that you might have. When you start Googling, you might find some sane advice on the internet, and these are what I would say just general good coding habits, like sort of the ones, the two ones at the top and the sane ones. So it's things like, if you have a condition in your for loop, don't evaluate that every time. If you're doing something, if you have a count in there or something, don't evaluate that every time you loop through the function, do that beforehand and then just use a constant in the loop body or in the loop condition. That's pretty sensible. Watch out for DB queries. That's kind of very common if you've got really long if and else ladders and really long if statements or else statements and really long blocks. And then maybe somewhere inside, you're doing a select something from the database that you could have just done at the beginning for multiple IDs instead of just looping through the IDs and doing that one by one, something that it's easy to miss. And then you get into the things like, use is empty array instead of count array equals zero zero or use a triple equal instead of string compare for string comparison because it's faster. And then use single quotes instead of double quotes when you're not concatenating, which is my absolute favorite. And then people say, oh use regex instead of some crazy string mangling that you do manually, but then don't use regex because it's less efficient than the native PHP functions. So you're all like, what? And coming back to the list of issues, none of those things actually address any of this. None of those tips will help us actually sort solve the easy issues. And on one hand, you don't want to ignore these things because they are just good coding habits. You might as well follow the good habits. And even if they might seem insignificant on their own, sometimes a lot of tiny optimizations might actually add up to something actually making a big difference. So don't necessarily ignore them. And this kind of stuff might matter if you're not actually addressing an actual problem with a memory leak. Maybe you're just trying to squeeze out like the last bit of performance juice out of your app. Then yeah, maybe those tiny optimizations will actually make a difference. But you know, is it really how most of us should go about optimizing performance? And just to show you this, so these are some stats taken from, I've got a link here, I've shared the slides already so you can check out the whole website. So this is from Thimo Matig's website. So he runs PHP benchmarks live on his server. The server is running PHP 5.3, but it doesn't really matter because it's kind of the same thing in seven even. And he's just compared to the typical things that you find on the internet and just to see if this is actually true. So this is the single and double quote myth. And you can see here that you're only actually shaving off two milliseconds. I mean, that's a drop in the ocean. That's not really causing your memory issues. And there are some other ones. So, you know, a lot of us might wonder like, oh, is variable before the constant? You know, should the variable go before the constant and on an equals comparison or the other way around? No difference whatsoever, three milliseconds. You know, if you're doing hastag zero is n or string position to just check the first character, no difference. String replace compared to preg replace only three milliseconds difference. I mean, that's not a huge difference. And all of those tips, what they all have in common is that they're all micro-optimization. So I would say 99% of the time, ignore them every time. It is worth looking into those things when you have evidence that they are actually causing problems or they are a bottleneck. And you should try to micro-optimize maybe bits of your code, not the entire app. I mean, don't go off and start changing the double quotes to single quotes everywhere. And that's probably not the way to go. It's probably not worth spending time on or arguing about, I mean, I've had so many pointless, it's like Vim versus Emacs, which quotes should you use? And you should always benchmark and measure to prove that you're actually improving situations. Don't just do this because you've read about it on the internet. Somebody on Stack Overflow five years ago said that that's what you should do. No, always measure, always prove that you're actually doing something right. And most importantly, don't miss the real bottleneck. Like don't focus on these things because there's probably some more important issues to tackle. So focus on the whole paths in your code or whole areas. Well, that's all good and well and good. Focus on the whole paths and areas if you know how. But the how is what most people get stuck on. And I got stuck on that as well. If you already know how, that's all well and good. But really, how, how do I know? How do I know, want to improve? How do I know how my app is doing? How do I gain that visibility into my code? How do I know what my app can handle in production? How do I know it's not leaking memory? How do I prove that? No, will I run out of memory? Or maybe I am over provisioning and maybe my app doesn't need the massive server that is running on. How do I prove my guesses? How do I prove my theories? And the general answer is of course measure. But then, how do you measure? And what do you measure? And how often do you measure? So I was there myself, I had all these questions. So I thought it might be useful to do a practical example of maybe how you could go about answering those questions. And how do you actually go about doing it? So let's start with the tools. And this is potentially the first very confusing, very overwhelming things when you're starting out because there are so many of them. And people tell you, use this, use that, use this. And you're like, well, but what is everything for? Like, how do I, you know, why, when should I use this, when should I use that? And it is important to know what each tool is useful for. Like, what's the purpose of a tool? And I've kind of tried to group them here by roughly what the purpose is. So you've got your monitoring tools at the top. There's things like Datadog, New Relic, Black Fire IO, your typical Elk stack, Graphite, loads of those things out there. And you might be lucky enough to actually already have something like that set up for your system so you may already have some performance data available. You know, if you've got something monitoring your memory usage or CPU usage, for example, you've already got some data. And that will give you the bigger picture, the idea of how your app is doing in general, how it's doing over time, has it gotten slower, hasn't gotten faster. And you might be able to tell from those graphs and from those tools, if there is anything that you need to worry about, you know, if you see spikes, if you see something worrying, then you probably know that you should look at your app. If you don't have any production data available, you could use the load testing or the benchmarking tools. And those are basically the tools that will generate the load for you. And there is a million of them. I've only listed here a bunch of them. My favorite one is Go work for simple things. That's a Go app. And it's just a really simple, simple to use app. There are some more complicated ones that take a little bit more time to learn and use. So things like JMeter or Gatling and Taurus or Apache Bench, they allow you to do a lot more. They allow you to customize your load. They allow you to specify different prams, the vary the load, that sort of thing. But they are a little bit more difficult to set up. And sometimes, you know, just a really big loop in your code, like for 10,000 iterations, do this. That's also load generation, you know? So you can actually just do it yourself. And the goal here, like the goal of those load testing and benchmarking tools is really just to avoid sitting there and refreshing your app multiple times, especially if you want loads of data. Like you're not gonna sit there and press the refresh button 10,000 times. So that's what those tools are for. But monitoring and benchmarking will only give you a higher level view and sort of higher level data about your app. They won't really tell you what's going on inside your app. And so to dig deeper, you need more detailed information. You need things like low level stats and stack traces and your memory usage and execution time for every single function in your code, ideally. So you need something called a profiler. And then usually profilers will do, like gather the data for you, but they will just spit out a bunch of files that when you open those files, they make absolutely no sense to you. So you usually need some visualization on top of it to actually help you make sense of all the data that you have. So how does that look for PHP? Well, for starters, none of this is built into the language, which is great. I mean, compared to Go, Go has most of these tools just shipped with the language. So actually when you install Go, you install all those tools. With PHP, when you install PHP, you install PHP. You don't have any profilers available. You could roll your own. I mean, to be honest, I'm not against just doing microtime at the beginning, microtime at the end, and then just to subtract them. That's how long my function take. I mean, for quick and easy and quick and dirty sort of measurements, I think that's completely fine. But if you wanted something, you're obviously not going to do this across your entire app. So to do that, you kind of need a more professional tool. And most of those tools are available as PHP extensions. Luckily, you don't need much extension food to get going. I mean, I'm not an extensions expert, and I managed to install a lot of them. It's quite easy. So just to recap, this is how you can install PHP extensions. I'll probably go through this pretty quickly, because hopefully you know this. Your mileage might vary, because obviously, depending on what platform you're in, how old is your operating system, that kind of thing, some packages may or may not be available. The actually most convenient way these days for me, because I run a Mac, is just to use Homebrew if I can for all the extensions. So if Homebrew or whatever package manager you have, like yum, apt-get, whatever your distribution is, you just do that, install, name, and boom, done. Your extension is on your system. You can use Peckle, which is part of Pear. So once you install Peckle on your system, you can do Peckle, install, name of the package. That should work. The S-word. Or you could compile from source, and that's actually not as scary as it sounds. So what you have to do is just get your source code down, so you usually get cloned or something. Go into that directory. You run something called PHPIs, and then you run something called Configure. This is kind of like Installing C apps. Whoever did C development, this will sound familiar, because PHP is written in C. Any options for the extensions, usually the readme for the extension will have those instructions there. So always check out the readme. This is just like a generalized list. Then you do make, make install, and then you need to not forget to put your any file in your PHPs, like conf.d, any directory, because that's how you actually load the extension into PHP. So you tell PHP, use that extension, and you can specify your any values there. If you do peckle or brew, those any files are usually created for you automatically, so you can just edit them. On the Mac, I believe it's user local, ETC, PHP version, and then conf.d, and that's where your files live. If you're using brew, homebrew, which to be honest, I recommend it because it's just the least painful way. So let's get ourselves a profiler. This is a list of the ones that I found. There is probably a lot more in GitHub. The first one, APD, that used to be an extension used for profiling and debugging, but it is actually no longer maintained. I think the last main paint update was 2004, 2007, so I kind of crossed that out because I think we should just forget about it. The two most popular ones, and I probably all heard about them, is ex-debug and ex-hprof, and then other people wonder like, what's the difference between them and which one should I pick? So both are installed as PHP extensions. They are both available via homebrew, for example, which is slightly easier. I'm pretty sure Apped Get has packages as well. And the deal with that is that in the old days of PHP 5, old, some of us are still on PHP 5, but in the old days ex-debug didn't have memory profiling built into it, so you could do your step debugging and you could do some profiling, but you didn't have the memory information, whereas ex-hprof, which was developed by Facebook, and then open sourced, that gave you all the memory information, so you kind of had to use both. But since PHP 7, Facebook is no longer maintaining ex-hprof because they've moved from Zend to HHVM, so they've abandoned that, and luckily some kind souls created PHP 7 forex of the ex-hprof extension. So the two probably most popular ones are Tideways, and again, I've put links here, I've shared the slides so you can click through it and find it on GitHub. So Tideways ex-hprof extension, which kind of is now just becoming their own product and their own thing, and they've slightly modified the original ex-hprof, or there's this Chinese person on GitHub that I will probably butch the name, so I'm not gonna attempt to read that, but that Chinese person has created a fork for PHP 7, which is actually very, very close to the original ex-hprof. And in terms of when you should use these, they are absolutely not mutually exclusive, so you can have both installed on your system and you can use both at the same time. The one thing to remember is ex-debug should only ever be run in development. I mean, it will make your app a lot slower if you use it, so don't ever run it on production. Ex-hprof can be used on production if you had to. It's actually, it's got such a small footprint that it won't really affect the performance of your app, so that's something to keep in mind. If you have to use something on production, it's probably gonna be ex-hprof. And then there are other things, like the memory profiler and the mem info, there are two other extensions that also allow you to profile your memory. We're gonna have a look at PHP memory info in a bit. And there is probably a lot more, but I just didn't have time to go through all of them. So, guessing really isn't enough. Like I mean, you might have some ideas to start with for what might be causing your issues, but you really need to work out and prove what's going on in your app. So you need to do some diagnostics before you do the diagnosis. Now, so I tried to find a real-life example to do that demo on, and then anything I worked on at work would just be too big and it would just take way too much time to explain what it does. And I would probably borrow you to death. And I also didn't want to give you an example with like foo and bar and class A and class B, because that's not really how real life is. I mean, we can all optimize a five-long PHP app, but that's just not what real life is like. So then a friend of mine, Matt, that you probably know, you probably know Branti, he tweeted about his site project. And I just randomly had a look and I was like, oh, this is a really lovely PHP 7 app that just fits that need perfectly to command line up. So I used Branti's app for my demo. Cigar, which is the name of the app, it's a smoke testing tool. So what you do is just you specify the URLs and the expected HTTP code and any expected content if you have it. And Cigar will just run through the list that you specify in the file and compare what you expected to get with what you actually got. And if everything's fine, you'll go, yep, I checked those URLs, I got the HTTP codes that you expected, I got the content that you expected, everything's fine. That's all it does. So you already know it will make some external calls to the internet. And you don't really need to know the full details. I'm kind of doing this on purpose right now because in the real life, very often when you're asked to optimize something or look at something, you might know very little about the app. You might not be the one, the person that originally coded that in. So you might even not know what the code does. You know, all you have is just some vague idea for what the app does. So let's imagine the first scenario. The app is running slowly. Somebody comes along to us one day and says, hey, that's a cigar tool. That used to take, you know, not 0.5 seconds to run. Now it's taking something like three seconds. What's going on? So, well, we can set up XHPROF to start with and see what that gives us. So the way you set up XHPROF is, this is on the readme, so you can just copy and paste from the readme. You basically just add a bit of code at the beginning of your file and then at the end. So the first bit is just enabling XHPROF and then the second bit is just disabling it and then saving the data to a file. And you can just copy and paste like I did here. So copy and paste in any file that you need. It doesn't have to be right at the beginning or right at the end. Like you can just profile bits of your app. You can check those two bits of code in a separate file and then include them or require them for every file to avoid copying and pasting this all over the place. Or you could use the auto-prepand and auto-append any directives to then get PHP to automatically prepend and append those two bits to your code so you just don't have to worry about it. And then XHPROF gives you a browser viewer. The original XHPROF gives you a way to view the results in a browser. The tidways extension, and that's the reason why I actually haven't used that extension for this demo, I think they've gotten rid of the original one because I couldn't find it anywhere. There is a directory called xhprof underscore HTML which is actually the HTML viewer. And that's not in the tidways extensions or at least I couldn't find it. So I think they might have gotten rid of it. In the original XHPROF, this is the original kind of UI that you get when you run it. And XHPROF will spit you out a URL when you run your app so that you can just click on it and go to this and you just give it a run number and some other stuff. I mean, this is all on the read me as well so you don't have to remember it. And that's the first page. So what you see here is, well, we've got the main function here and then we've got some of our code, some of our cigar code. We've got the parse function, we've got get URL objects and then we've got a closure here. So, and those are the top three ones here and they seem to be taking over two million microseconds. I think that's the unit that they use here. So the first most important thing, and that's again what a lot of people tend to forget is to establish a baseline. So before you actually go and change a single line, you need to know how your app is doing now so then you can compare after your changes, you can compare the results after your changes and see if you've improved things or maybe you've made things worse. So always make sure that you grab that baseline first before you go off and change things. So our baseline is the 3.0, 64 seconds that we saw earlier on. And then XhProf also, whoa, this is really bleak, but it doesn't really matter here. So XhProf also gives you a call graph. You can click on the call graph link and it will help, it will try to be helpful and it will try to show you a hot path here. So this one you probably can't see this, but this one is the parse function. The big red thing is the parse function. So it kind of agrees with what we saw on the list. So XhProf is kind of saying that, whoa, this parse function is taking the most time. But that's kind of as close as we can get to the code with XhProf. We don't really know what's inside that red box. We don't really know what the code is. So, but at least we know now what area to focus on. I mean, we know this that get URL objects function is something that is causing problems. So at least it allowed us to like zoom into the bit of code that we might be interested in. But then how do we find out what's going on inside that function? We could obviously look at the source code at this point, which is something that we might do. But let's use XDbug. So the way you set up XDbug, this is actually something that you get out of the box if you use homebrew. It's just three directives. You basically just enable it, give you the output directory for where to save your output files, and then just give the output name for the files. So you save the files as cashgrind.out with some random number at the end so you don't get clashes. And then what that will do is every time you run your app, whether it's a browser app or a command line app, XDbug will work and profile your app behind the scenes and save the results to a file. But then again, the file when you open it is just a random string of nonsense, really. It's not nonsense, but it doesn't make any sense to you. So there are lots of UIs to view XDbug data. I'm gonna use Qcashgrind or Kcashgrind, I think it's on Linux, which that tool looks like it's from 1995. I mean, it's been around for a long time, but it's actually the thing that still most people use as the defaults XDbug UI. There are other ones. There are paid versions on Apple Store. You can use something called Webgrind. So there are a few options, but Qcashgrind is actually not too hard to install these days. You can install it via brew with the caveat that you can't see because it's not visible enough. But basically on the latest Mac, if you install it via brew, because of the changes to security that they've done that you can't put binaries into user bin anymore and they are in local, Qcashgrind can't find some of the binaries it needs. So this is a workaround thing, and I've shared the slides already, so you can, if you run into this, this is a tiny workaround, you create a shell wrapper around it to get it going. So it does work on the latest Mac, even though it really looks like an app from kind of like 1995 up. So first of all, you have a dropdown here called, sorry, there is a dropdown with time or memory. So you can select, do you wanna view time information or memory information? So we wanna view time because we're now looking at time. And then we've got the same parser functions at the top here. So we kind of see the same results as XHPROF. And on the right, you've got something called the color map, and that gives you this weird looking pyramid diagram. And it might look very confusing at first, but basically if you're looking at performance, what you're looking for is big squares with nothing in them, because that means that proportionately that square, that green square took the most time in the app, but it didn't call any functions. There was nothing inside it, no squares inside it, which means it was the last function that the profiler in the profiling trace that was found most of the time. And when you hover over it, that hover is the parse class here. So again, the way you read it is those little squares here, you might think that, oh, that's really busy, that's something going on in here. That's actually nothing to worry about. That's just lots of functions calling other functions and taking a relatively small time overall. So what else can we get from Qcache grind? Well, at the bottom there is a tab called call graph. So you can get the call graph and we see the same things as XHPROF. And then we can see here that this one is the closure. So this is taking a fairly long time. And closures are one of those things that are really, really hard to debug when it comes to performance because the tool won't give you a name of a function right away to look into. They will just say like, oh, in this file, there's a closure and sometimes you might need to work out which one is it? So that's a little bit tricky to work out. And then if you compare the two, I just did it for just out of curiosity. Comparing the two call graphs, they look the same. So they are from two separate runs. So the overall time parse in XHPROF is shown as 81% of the time. And then here it's 79% of the time but that's because it's two separate runs. So your data will vary between the runs slightly but we do kind of get the same general idea that that parse function and that one seems to be taking quite a while. So back to Qcache grind. We select the second function from the top. So the top one is main which is always gonna be at the top, obviously. And then there is the array map function. And here you can see, so the top section here is the colors of that function and then the bottom one is the callees of that function. And in the callees, you can see that closure. Well, you can't see it because it's probably not visible but that has the closure in the list. So then you can do even more cool stuff in Qcache grind. You can view the source code here. So when you click on the view source code, well, some weird stuff like high equals four, that kind of thing. Or maybe somebody might have put some code in there to calculate pi as well as fetching the URLs in the get URLs for objects function. So when you then look at the code, that's actually, I've just pasted that in, it wasn't mad, it was me. So I just made it slow on purpose to make it show in the graphs but that's kind of how you arrive at what bit of code exactly is taking the most time. And so then when we remove the pi code, we've improved things, it's back to 0.3 seconds. Ooh, surprise, surprise. So we've got our proof here. We've actually, you know, we've got some numbers. So it's not just me saying, oh, I think that was it. You know, I've actually got proof that I've got it down from three seconds to 0.3 which is kind of what you want. And then just for completeness, we run XH prof again. This is the top of the list of the functions. It doesn't show up there anymore. There's no parse here, there's no parse here. And then somewhere at the bottom, there is the get URL page and it's now, so now it's something like 737 microseconds versus the over 2 million milliseconds. So XH prof is saying the same thing. We've made it better. And again, this doesn't really show up too well but this is the call graphs and now, so this is the old one where the parse was here and then parse is somewhere here which you completely can't see, I apologize. But now XH prof is showing us a different hot path that it thinks might be now the busiest path and you also can't see it, but this is all Guzzle classes. So Guzzle is an HTTP library that you use for making requests and then we know that this app is likely to make external requests. So this isn't necessarily worrying because, well, it's what we expect from this app to use Guzzle and to mostly use Guzzle to do its job. So this is not alarming anymore. One thing I would say is don't discount library code from when you're optimizing performance because the fact that it's a library, it's still code, it can still have bugs, it can still have memory leaks. I mean, at work, we've had a memory leak in one of the metrics libraries that we've used. So when you're looking at performance, just because you see some familiar names like, oh, it's Guzzle or it's Symphony or it's Zend or whatever, you know, most of the time you can trust that they've done a good job and they don't have any issues and it's just because the app, you know, mostly makes external calls, but it could be, you know, if you spent hours looking at something and all your clues point at a library code by all means look at it because it might actually have some issues. And they make a pull request, obviously. So then back to Qcashgrind, we've got parse. We don't have parse at the top. There is a handy search as well, so you don't have to look through it. Now we're near at the top. This now looks different and now this is also Guzzle so we can see that Guzzle is taking proportionally the most time. So this is kind of what we expect now. That was a good spare step. You know, that was like, yeah, we've done something good. By the way, you can also view XDBug profiling things in PHPStorm for those of you who use it. In tools, you've got something called Analyze XDBug Profiler and that gives you something like that. It gives you nowhere near as much information as Qcashgrind, so it's not really a replacement, but if you just wanted to view something quickly, you can do that. It gives you something called like a call tree so you can like in a typical PHPStorm way, you can do the drop down drill into the drop downs. One caveat with that is with PHP 7.2, the stable PHPStorm version doesn't actually work with this. It can't open the XDBug 2.6 files. So I had to install the EAP version and the EAP version seems to be fixed for that. So I think it's in the 2018 release that they will actually put those bug fixes in. So just to keep it in mind that you might have to go for the EAP version if you wanna use this for XDBug. So what more can we get here from the XDBug output and what more can we see in Qcashgrind? Well, one of the cool things it shows you is this column shows you the number of times a function was called, which is really handy sometimes. So here, for example, we've got 123 calls to string to lower, and you might be wondering like, well, why is that? And then again, you can look at the colors list which is really handy because that shows you all the classes and all the functions that have called PHPStringToLower, which is quite handy. And here we can see that, so all of these are again, guzzle functions, and all of these functions here are to do with headers. So it's something like set header, get header, add header, that kind of thing. So we can guess that guzzle is lowering all the strings using that function when it's setting the headers. And again, because it's doing a lot of external requests, it's no wonder that it's doing over 100 calls to that function. So the reason why I'm showing you this is that you can really gain a lot of understanding of what your code is doing when you do something like that. You can click around here, all of this is clickable. You can just click on these things and get an understanding for what's exactly calling this. Is this something that should be worried about or is this normal? And I don't know what else can we see. So we can see that we have nine calls to the fopen function. And well, we know that our, the Cigar app is only opening a file once. It's got one config file that it will read the list of URLs from. So we'd expect more like one call to fopen. And again, looking at the colors list here, we can see that this is our get file contents function, which is actually the one that, the config file that we are reading. There is also some guzzle things. And again, you might be wondering what is guzzle open doing with fopen? Like is it opening files? No, maybe it's some temporary files that it writes down. And again, you can look at the source code and you can see here that is the PHP temp. Some writes to PHP temp. So then now you know that guzzle is actually writing some temporary files as part of its work. And again, that kind of visibility you can gain by just clicking around. So now we can flip the dropdown here to memory and come back to our get file contents function. You can search for it or you can just find it in the list. So we click on that and you can see that the, we do like an fopen, fread and fclose and that seems to be taking quite a bit of memory. And I wonder, because since we, and this is kind of the part that mostly just comes with your experience or how quickly the light bulb is gonna go off in your head. But when you think about it, we're only reading from the config file. We're not writing to it. And there's a different function in PHP for doing just reading from file called file get contents. So again, you might be wondering, well, is it gonna be faster if I use that function instead? Or like, is there gonna be any difference at all? And so we swap that function and then sure enough, just file get contents here. Now gives you instead of over 8,000 here in the memory, forget what the units are, I think it's bytes. Now it's like 496. And then overall, if you look at the colors list, so the parse function, which then inside calls the get file contents, that was 512 before and now it's 496 now. So again, you know now that using that function is faster in your case. So you should probably go with it. But again, you've got the hard proof. You've got the numbers and this is actually better. And this is so easy to play around with as well. Like you can, all you have to do is edit your code. Your ex debug is running in the background. You just run your app again, you get a new file, you open it in cash grind and you've got different results. So this is a really quick feedback loop. It's not, we're not talking like minutes to compile or anything. So it's really easy to play around with this. If you wanna play around with using different functions, see if it makes a difference, maybe it does, maybe it doesn't. And by the way, for this case, XH Proph wouldn't show us any of this information. So in the UI, you can click through things. So when I click on the get file contents thing, function from the first list, this is as far as it gets me. Clicking on that link takes you nowhere further. So this is all you get. And this is the call graph and it just shows you that parse function that then calls get file contents. But you don't know anything more than that. Like F open doesn't show here. So you wouldn't get that information from XH Proph in this case. And this is why you should really use all the tools. I mean, don't just stick to one because sometimes it might help you, sometimes both might show you the same results, but sometimes one of them might be much better than the other or show you more information. So by all means, go ahead and use as many as you want or try out new ones. So what if you want to do some general explorations? I mean, those were things that we could kind of spot as maybe potential problems, but what if you wanna just know what's inside your memory in your app? Maybe there is something that could not be there. So, and how do you check that your program is not leaking memory for sure? You can look at the memory numbers in XDbug or XH Proph and kind of guess on that, roughly how much memory you're taking and does it go up between the runs? Does it not go up? You could watch your monitoring graphs, your production monitoring graphs. Obviously, those are good things. And if you look at the GC stats, for example, if you see something like this, so these are all the GC phases, that's okay because the overall, this is flat. So as long as this stays flat, that's fine. This is just GC doing its stuff. But it would be nice to know what's in our memory at the end of the run of your program. Like what exactly, what objects are there when you finish running your program? So for that, we can use the PHP Mem Info extension. Again, once you install it, you just need to add one line. So at the end, this basically just tells to save the output to a file. It saves output in JSON files, so you can actually open them and make sense of them, which is nice. So this is all you have to do. You just do Mem Info dump, and it will shove some information into the file that you can then analyze and explore. It also comes with an analyzer that is just a PHP app. So it comes bundled up with everything you need. So let's add that to the end of the parser file and the, sorry, not the parser file. So this is the entry, whoops, the entry file to Cigar. So every PHP app is gonna start with the procedural file. So this is the main binary file for Cigar. So we just added at the end of the main file. So we know that we're gonna get the data at the end of the whole app. And then you use the analyzer. So it's just a binary that you run. And you've got a summary call, for example, which is a good one to start with. So you just do summary and then the name of the file, and it gives you something like this. And it gives you the exact count of all the objects that are in the memory when we dump the output to the file. So we can see how many instances of everything are there. I mean, there is some outputter and parser, which is the Cigar code. And there are strings, array, integers, there is URLs, there is results, all sorts of things. We've got 44 strings, for example, this is how much memory they are taking. It's pretty useful. And the cool thing about it is that if there was a memory leak, you'd probably see an object that you wouldn't expect to see here. Like if there was an object that you would think, like why is it here? And on top of that, if there was like 100 and something instances of it, that might be a warning sign. So this is quite a good output. Like we see nothing warning, alarming here, but if you did see like a random object that you would think it should be long gone and there's 124 of those things and maybe there shouldn't be, that's a really good tool for that to spot any of this. And by the way, for this demonstration, I tried to create a memory leak to show you like an actual memory leak here, but it's really, really hard to trick PHP 7 into memory leaks these days. Like all my tricks I've known from PHP 5 don't work for PHP 7. So if you leave open file handles, if you leave circular references to objects, none of this works in PHP 7 anymore. It's gotten a lot more clever. So let's just for the sake of the demonstration and see what we have here. Well, we've got our favorite parser object again. But then if you think about it, when we look at the source code, we create the new parser objects and then we parse the file to get the config, so get the URLs that we're gonna call and then we do the calling. Like we do this asynchronously. So we only read the file once so we only need the parser object once and never again, because we only read the file information once. So why the hell is it still in memory at the end of it? It could be that because overall your program is not taking a huge amount of memory, the GC just doesn't get to it. Like maybe if there was more memory the parser object would get cleaned as part of it, but maybe not. So why is GC is not cleaning it? Like why is that object still there at the end of the file when we only use it here and we don't really need it anymore? So it looks like we are creating a variable here and we're using it here. And when you look at the query command in the analyzer, you can filter it by class as well so we can zoom into the parser function specifically. Then it gives you all the information about that particular object in memory. So it gives you the ID of the object, tells you that it's an object of this class. There's two references to it and it's a root so it's not a child object. And it's in the global execution frame which is what we would expect because we're in the main procedural file so it's a global scope. And then can we dig deeper? Like well we can, we can use this ID in the next command which is the ref path command which shows you all the references to that object. So we have two references here. One of them is the self reference here which is the self reference from the object to itself and every PHP object will have it. And then the other one is from the global scope to the parser object. So that kind of gives you an idea for, if you wanna find out, if you've got lots of objects in your memory and you wanna know what is the reference that is preventing this object from being GC'd, then that's a great tool to use because that will show you exactly what is still referencing that object. Like in this case this is not alarming, this is just self and global, this is what we expect. But if there was something suspicious, if like something else was referencing this object or that we wouldn't expect it should show up here. So that gives you a good idea. So out of curiosity we can look at the global's ID as well. So in the previous one you can have the ID here for the global scope and if you do that you can see all the familiar PHP things. So this is a getPost, cookie, files, request server. All of that stuff is here in memory in the global scope. So can we make that parser object go away? I mean we know we don't need it. So is there a way to do that? Well PHP gives you the option of creating that object on the fly and then calling that function on the newly created object but without really keeping a variable attached to it. So does it change things if we do that instead? I mean we know we don't need that parser variable anymore. So then we run the analyzer again after we run the summary command again and then it's gone, it's no longer in memory. So again we've got the hard proof that we've done it and it's no longer here in memory. And this is something, this is a good trick to keep in mind that sort of dynamic reference. If you know you're creating an object just for a single use and you might not need it anymore especially if it's in the global scope and you wanna make sure that it doesn't necessarily hog the memory, that's a good trick to keep in mind. And again, you can run your analyzer to then find out if it's actually working or not. So just to make it clear, I have to make it clear, none of these things were in Brunti's original code. He didn't make any of those mistakes. He's done his performance homework. I have made a lot of edits to this code to make it bad. So thank you Matt for letting me use this. So I hope it was useful to see the demo like this and I hope it seems a bit less scary now. There are many, many more tools out there. All of them have read me so you can probably work out how to run them. It might seem a bit, I mean this was a small demo and I already knew where I was going with things. Normal performance optimization might seem very tedious at times. It might seem a bit daunting but it is so rewarding at the end of the day. It's really worth it. I mean, the feeding that you get when you get the final result and you go, yes, I've got it down from like three seconds to not point five seconds. It's really worth it even though it might seem a bit tedious. Some key takeaways just to summarize. Prioritize what to focus on. Look at the gains versus effort. It would take to optimize something and focus on the most important things first. I mean, the reality for most of us is that we're given a specific time to optimize a project and you're just never gonna do everything that you want to do. So just focus on the most important things. And ignore micro-optimizations unless you really, really have a good reason to worry about them. Design changes sometimes redesigning your whole app like actually changing how it works is way more important than single code changes or your flipping of your single quotes or double quotes. Usually try to focus on the hot paths. I mean, you can use the XH Pro as a guidance for what's a hot path but not always. Sometimes the hot path is there because that's the nature of the app so definitely don't ignore the rest of the tree. Don't sort of take it for a silver bullet always. Customize your optimization for your needs. I mean, there is usually more than one way to optimize. There's usually more than one correct answer to make something better and don't be afraid to break the rules. Don't be afraid to break the rules if it works for your use case. So it's kind of the same thing as denormalizing databases. I mean, in theory, you shouldn't do that because you're denormalizing things but actually if it works for your particular use case, then you should do it and it's the same with performance. I mean, maybe you're not doing something textbook correct but if it works for your case, if it makes it up faster, don't be afraid to do that. Use the data to prove your guesses, measure everything. Keep in mind to do this with minimal impact on performance, especially in production. So try and avoid profiling in production if you can. You can just do all of that stuff in your web development but always measure everything. Always have the benchmarks to prove your guesses. And remember that performance optimization is an iterative process. I mean, it's not enough to do it once. You usually have to come back to it over time. You need to know your trends over time. You need to observe how they change over time. So have monitoring in place. Maybe have some alerting in place based on the trends. You could run your automated load tests to prevent degrading performance. So you could run it after every commit even to make sure that it doesn't make things worse. And for the VIP experience, this is something that Etsy does and they have a really good blog post about it. They actually have a graph which shows the memory usage and they've correlated that with deployment. So actually they have vertical lines for every time they deploy something. And then you can easily see that after this deployment, the graph suddenly spikes. So you know exactly what deployment, which deployment made things worse. Which again, just helps you narrow things down a lot faster. That is all from me. Thank you very much. Thank you.