Viade Afterburner is a freeware Microsoft® Visual Studio® extension/add-In designed to add some “missing” functionality to Microsoft .NET Framework’s feature set:
There are numerous development tools, utilities, libraries and code snippets out there vying for a programmer’s attention and offering to simplify software development process. Why would you want to look at yet another one? Unlike many, Afterburner requires minimal investment of your time and effort in order to benefit from it. There is no need to learn a new API or make any changes to your existing code. The only requirements are to install Afterburner and to check off what features are to be applied to the selected projects in your solution no matter what .NET language is used. The chosen features are being automatically injected into the selected assemblies during the post-compilation Afterburner step “thereby increasing the thrust” of your application.
Programmers have been hitching a cheap ride on Moore’s law for the longest time. Hardware guys were picking up most of the tab by coming up with more and more ingenious solutions designed to push CPU clocks ever higher to give the existing applications a “free” performance boost. However, now that Moore’s “law” is being pitched against the quantum laws of physics, hardware engineers are being forced to introduce more cores and CPUs rather than making individual CPUs faster. The ball now is in the developers’ court. To keep up with the users’ demand for increased performance, more and more programmers are forced to take advantage of these extra cores. This predicament naturally brings us to the multi-threaded programming realm and all the glory and the perils that come with it.
One of the many difficult tasks facing a developer that just started working on a mature multithreaded application is to understand its design from the point of view of its threads’ interactions. What threads are created or are entering the system? What classes are the key players controlling the synchronization between these threads? What synchronization primitives are shared by which threads? Changing threading related code without good understanding of these matters is asking for trouble. Afterburner’s Threads Map feature, based on information collected during application execution, generates an interactive UML-like diagram that attempts to answer some of these questions. The diagrams show threads accessing the system, key classes visited by these threads and synchronization primitives shared by them. With the help of the created diagram a developer can within minutes zero in on the most relevant players and relations instead of spending countless hours debugging and sifting through the source code to achieve the same level of understanding of the application’s threading architecture.
Multiple threads must share data structures in a mutually exclusive fashion in order to avoid corrupting them. While a given thread manipulates a shared data structure, it owns the exclusive rights to access it. All other threads seeking access to it must wait for their turn. If a cycle emerges out of a thread owning some data structure while waiting for another one already owned by some other thread in the cycle, the application deadlocks. Normally, after establishing that a “hung” application is actually deadlocked (not a simple feat in itself), the developer is faced with a daunting task of examining multiple call stacks in the system in order to determine what threads and synchronization primitives are involved in the deadlock. Moreover, such an examination is hardly possible if a deadlock is encountered on a computer without all the necessary tools installed on it in advance. Afterburner’s Deadlock Detection feature alerts the running application when a deadlock is detected by throwing an exception describing the complete cycle of threads and synchronization primitives participating in the deadlock with all the relevant call stacks. This information should be sufficient to understand the nature of the encountered deadlock in order to eliminate its possibility.
Deadlocks usually are quite hard to reproduce since only a particular timing of events forces a buggy application to actually experience them. There exist coding strategies that can guarantee the absence of the deadlocks all together. A commonly used one, called lock leveling, is to come up with a designated ordering of all synchronization primitives. Each thread that needs to exclusively access more than one of the shared data structures at once must acquire exclusive access rights to the corresponding synchronization primitives in the designated order. Well… easier said than done. In complex multi-layer multi-component applications it is easy to lose track of the acquisition order and to violate this strategy. This is where Afterburner’s Deadlock Prediction feature comes handy. It’s a dynamic analysis tool that tracks exclusive access acquisitions made by the threads in the running application and generates a report of the acquisitions performed in mismatching orders. More likely than not such a report is an indication of a potential deadlock condition and warrants a careful examination.
Even though object lifetime management is not a problem specific to multithreaded development, involvement of more than one thread certainly does not simplify things. Framework’s garbage collector (GC) alleviates many of the memory related issues that plague unmanaged code, but it does not solve all of the related problems. Sometimes your managed objects need to explicitly control creation and cleanup of certain resources (database connections, operating system handles, etc.) that are scarce and need to be cleaned up as soon as they are no longer necessary. Relying on GC to clean things up is not an option due to its nondeterministic timing. Such objects should implement IDisposable interface to allow their consumers to clean things up eagerly. IDisposable only provides the mechanism – it is still up to the consumer to actually call the Dispose() method at appropriate times. If Dispose() is not called the code still appears to execute correctly, albeit not as efficiently as possible. Afterburner’s Dispose Monitoring feature generates a report listing objects that implement IDisposable interface but whose Dispose() method was not called explicitly before GC got to work on them.