This time I wanted to share some of my experiences with the performance tooling of Visual Studio 2015 with you. This is going to be a short post, just to let some of you know what is there in Visual Studio, a lot of you will probably already know about this.
A long running project of mine is a multiplayer stakeholder game that visualizes heat networks. The graphical interface is being developed by a third party but I and the company I work for are developing the central calculation engine that performs the complex algorithms and integrates some other components that are needed for getting all the data ready for the front-end.
This is all working fine, but during the start of the new year we also needed to integrate the gas networks. Now both network types are very similar, on a more abstract level they are both networks that contain nodes (like hubs for pipes), pipes and connections (special nodes, that give entry into the network, usually houses are built near these). So we thought, with some modification to the gas network data, we could just use all our existing algorithms and calculations (to calculate the needed pipe diameters for example) with the gas networks.
There is one problem, the gas network are like 100 to 1000 times bigger than the heat networks. While everything we did was working fine it literally took ages. Responses were really getting slow, up to the point of one minute when starting a new gaming session. We really needed to something about this.
The first tool that really helped me, was just the visual studio debugger. I took my time, placed breakpoints on the entry points on everything that was slow, and the debugger really did the rest.
In the image above you can see the response time of this method. If you are interested in drilling down, just step in the method with the debugger, and on every step you get to see the response times. It’s great to really fast diagnose the slow parts of your application and do something about it. The values include debug overhead, but are nevertheless useful to get a sense of how different actions are related to each other in sense of time.
When you are running the debugger, you also get another really cool window in visual studio.
This window also has some really useful information. In the top most line, with pause sign in front of it, you see different break events recorded. Like when hitting a breakpoint. The response times that you see when stepping through a method, like the first image, are also recorded there. This part actually collects intellitrace events. If an exception happens for example, you can double click here and start historical debugging.
I found this window the most useful for seeing the memory pressure and how many garbage collects were happing. You can also use this tool to diagnose memory leaks. You create a snapshot, then you do some stuff, and then you create another snapshot.
In the figure below, I took two snapshots. One before starting a session, and one after. And then I compared them. The snapshots actually perform a garbage collect also, so this is the ideal way to see if objects are lingering.
You can do all kinds of things with these snapshots. Compare them with more detail, drill into their heaps to see what’s there etc. But right from this picture you can draw a simple conclusion: There is a memory leak. If there wasn’t the two snapshots should contain roughly the same amount. So after my last actions, a lot of objects seem to be lingering, they are not garbage collected because they are rooted, they are reachable. For my application this was expected, each started sessions stays in memory until it’s done, all database IO is being done asynchronously by a separate process, to keep the performance of a running game high.
When running the diff I get the following picture:
I sorted this on which object types have been increased. Here you can see that a lot of session values are still there. This makes sense, since creating a new session will give us a lot new calculation values. But if I wanted to diagnose a leak, this would help me find it really , really fast. You can also view the paths to the root here (which explains how an object is reachable), view all it’s references, and even go to the code definition!
Using the debugger only I managed to speed up the application at least 4 times, by finding slow parts and executing them a lot fewer times or speeding them up by implementing them a bit smarter.
I also found out that a lot of slow parts came from searching lists of objects based on an unique value. Often it’s much faster to convert these to dictionaries and then use these to find the object or check if it exists. Never forget about the O(n) and O(1) notations you learned about in your data structures class!