Introduction
In this article, I'll try to demonstrate how you can use Aspect Oriented Programming to develop a non-intrusive, simple benchmarking application step-by-step.
The article comes with a sample C# solution (source code included) with the following projects:
- Benchmarking: a class library project which provides general-purpose benchmarking facilities.
- MyApplication.CommandCore: a library containing a calculator which implements the Command Pattern. Hence, it can performs undo and redo operations.
- MyApplication.CommandClient: a console application which references the MyApplication.CommandCore project and retrieves performance information for the calculator operations.
- Sorting: a class library project containing various implementations of sorter objects.
- MyApplication.Statistics: a class library project containing a single class (
Statistics
) which performs statistics calculations. - MyApplication.SortingStrategy: a Windows Forms project which references
MyApplication.Statistics
and retrieves and displays performance information for the Sort
method of the various existing sorting algorithms.
Certainly, there are many available frameworks which provide performance functionalities. So, this article doesn't aim to reinvent the wheel by providing a new benchmarking tool. Rather, the focus is on the opportunity to explore areas where AOP can be very useful.
Background
Defining AOP
Since I used the Spring.NET Framework to develop the sample code, I'll use the Spring.NET definition to describe Aspect Oriented Programming:
"Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking about program structure. Whereas OO decomposes applications into a hierarchy of objects, AOP decomposes programs into aspects or concerns. This enables the modularization of concerns such as transaction management that would otherwise cut across multiple objects (such concerns are often termed crosscutting concerns)."
The above definition mentioned transaction management as an example of common concern (or aspect) between programs, but we could also enumerate other concerns which cut across multiple objects:
- Logging,
- Security,
- Mock frameworks (such as in TypeMock),
- Exception handling, and of course...
- Benchmarking measurements
The AOP Jargon
For reference and clarification, I'll enumerate some of the AOP concepts used in the article (also according to the Spring.NET documentation):
- Aspect: A modularization of a concern for which the implementation might otherwise cut across multiple objects. Transaction management is a good example of a crosscutting concern in enterprise applications. Aspects are implemented using Spring.NET as advisors or interceptors.
- Advice: Action taken by the AOP framework at a particular joinpoint. Different types of advices include "around," "before", and "throws" advice. Advice types are discussed below. Many AOP frameworks, including Spring.NET, model an advice as an interceptor, maintaining a chain of interceptors "around" the joinpoint.
- Pointcut: A set of joinpoints specifying when an advice should fire. An AOP framework must allow developers to specify pointcuts: for example, using Regular Expressions.
- Target object: Object containing the joinpoint. Also referred to as advised or proxied object.
- Before advice: Advice that executes before a joinpoint, but which does not have the ability to prevent execution flow proceeding to the joinpoint (unless it throws an exception).
- After returning advice: Advice to be executed after a joinpoint completes normally: for example, if a method returns without throwing an exception.
The Undoable/Redoable Calculator
The MyApplication.CommandCore presents an undoable, 4-operation calculator class that is used by the MyApplication.CommandClient project. You can perform operations and then call the Undo(n)
method. Thus, the calculator will roll back the latest n operations.
The top level object in the calculator project is the User
class. This is how the client application performs calculator operations:
class Program
{
static void Main(string[] args)
{
User user = new User();
user.Initialize();
user.Compute('+', 100);
user.Compute('-', 50);
user.Compute('*', 10);
user.Compute('/', 2);
user.Undo(4);
user.Redo(3);
Console.In.Read();
}
}
The above code (until the sum operation line) would look like like this in a sequence diagram:
Okay, now let's say you are interested in measuring the performance of your calculator. For this simple task, you could start by adding lines of code at the beginning and at the end of your User
class, so that you could later compare the times and calculate the execution time:
public void Compute(char @operator, int operand)
{
RegisterThisEntryTimeSomewhere();
Command command = new CalculatorCommand(
calculator, @operator, operand);
command.Execute();
commands.Add(command);
current++;
RegisterThisReturnTimeSomewhere();
}
But wait, we're not done yet. The User
class still has other methods, like "Undo
":
public void Undo(int levels)
{
RegisterThisEntryTimeSomewhere();
Console.WriteLine("\n---- Undo {0} levels ", levels);
for (int i = 0; i < levels; i++)
{
if (current > 0)
{
Command command = commands[--current] as Command;
command.UnExecute();
}
}
RegisterThisReturnTimeSomewhere();
}
Hmm, seems like we are in trouble. Are we going to change every method we're trying to measure? Obviously, changing every method of our cool objects is not a good idea. In addition, there would be cases when we couldn't even change the code of the objects we are trying to measure. This is where Aspect Oriented Programming can be of great value.
The Benchmarking Project
Let's create a new project, called Benchmarking, in order to provide AOP functionality to enable general-purpose performance measurement to our client application. Let's write down the guidelines for our new Benchmarking project:
- It should use Spring.Net AOP functionalities.
- It should provide the user with an easy interface and non-intrusive AOP capabilities.
- It should expose functionality for creating new proxied objects. After a proxied object is created, it should start registering a time sheet with execution times for the object members we want to measure, according to a configuration file.
- Each row of the time sheet will contain the following information: class name, member name (i.e., method/property name), argument list, and elapsed time.
- We should be able to retrieve the time sheet whenever we want.
- We should be able to reset the time sheet so that we could start over the performance measurement.
The BenchmarkFactory
starts by instantiating a static ApplicationContext
for IoC Container (= Inversion of Control Container, a technique which implements the Inversion of Control Pattern. See more about IoC Pattern in Billy McCafferty's excellent article Dependency Injection for Loose Coupling). Inversion of Control will provide us with the flexibility of instantiating different class just by changing the client application's XML configuration file.
private static IApplicationContext ctx = ContextRegistry.GetContext();
private static List<timerow> timeSheet = new List<timerow>();
The core of the BenchmarkFactory
class is the GetProxy()
method. It has a single line, and returns an object created via the IoC container technique provided by the same Spring.NET framework:
public static object GetProxy(string objectName)
{
return ctx[objectName];
}
When the GetProxy()
method is called, the AOP framework generates a dynamic proxy for the requested class, with additional code that enables the object's members to be intercepted during the invocation lifetime. The interceptor is the BenchmarkAroundAdvice
class of the Benchmarking project.
Back to the Client Calculator Project
Now that we have a benchmarking layer, let's start changing our client calculator project. First of all, the client calculator project will have a new app.config file holding the configuration for the AOP framework. This configuration information comprises:
- The information about the proxied object: the full name (namespace + class name) of the proxied class, the assembly name, and the interceptor names.
- The Around Advice information: the full name of the advisor class, the assembly name, and a pointcut with the mapped names which defines which class members are going to be measured.
<object id="aroundAdvisor"
type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="Benchmarking.Aspects.BenchmarkAroundAdvice, Benchmarking" />
</property>
<property name="MappedNames">
<list>
<value>*</value>
</list>
</property>
</object>
This is how we avoid the cumbersome task of changing every class we want to measure...
Now, we should replace the instantiation method of our user object. Instead of using the new
keyword, we will use the BenchmarkFactory.GetProxy()
method:
IUser user = (IUser)BenchmarkFactory.GetProxy("user");
Notice that the above line is needed so that the AOP framework could create a dynamic proxy that intercepts the beginning and the end of each method/property call.
Immediately after the GetProxy()
line, we must reset the time sheet for the BenchmarkFactory
:
BenchmarkFactory.ResetTimeSheet();
After we have finished operations with our calculator object, we can iterate through the time sheet rows, in order to list the performance for each method call:
Console.WriteLine("===============================");
Console.WriteLine("BENCHMARKING RESULTS");
Console.WriteLine("===============================");
foreach (TimeRow tr in timeSheet)
{
Console.WriteLine("===============================");
Console.WriteLine("Class Name = {0}", tr.ClassName);
Console.WriteLine("Member Name = {0}", tr.MemberName);
Console.WriteLine("Arguments = {0}", tr.ArgList);
Console.WriteLine("Elapsed time = {0} milliseconds", tr.ElapsedTime);
}
Console.WriteLine("===============================");
After the changes, this is the new sequence diagram for the application. Notice that the proxied IUser
object is now intercepted around each method call. Once the pointcut specifications are matched, the corresponding Around Advice class is triggered automatically:
Now, we can run the application again and see our performance results for the first time:
Visual Benchmarking
Let's open the Benchmarking project to create a Windows form representing the timesheet:
This new user interface is provided by a singleton form, and is useful when you have another Windows Forms project that references the Benchmarking project, and you want your Benchmarking results to be processed in the background but to be shown only when the user explicitly asks the application to do so (e.g., by clicking a "Benchmarking" submenu under a "Help" menu).
This interface also allows the user to copy the timesheet data to the clipboard. Thus, the copied data can be pasted directly to an Excel worksheet. In addition, the user can reset timesheet data whenever he/she wants.
The Windows Form Calculator Project
Now that we can show benchmarking data in a Windows form, let's create a new project (CommandUI) and implement a new Windows Forms interface for our old console calculator:
The new interface is cool. Now, we can perform the 4-operations, plus undo/redo functionality. But the great leap here is the ability to show benchmarking results. After some calculations, this is what we get when the Show Benchmark button is clicked:
Notice that two members of the User
class are displayed: Computed
and get_CurrentValue
. The Spring.NET AOP framework allows us to change the App.config so that we can specify what members we want to display. So, we open the App.config and replace the "*" value by "Compute" inside the "MappedNames
" property, for the aroundAdvisor
configurations.
<object id="aroundAdvisor"
type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="Benchmarking.Aspects.BenchmarkAroundAdvice, Benchmarking" />
</property>
<property name="MappedNames">
<list>
<value>Compute</value>
</list>
</property>
</object>
This is how the timesheet looks like after the change:
Unfortunately, the methods we are testing are too fast to be perceived by the benchmarking. What if we created a more challenging example?
The Sorting Project and the Sorting Strategy Project
Suppose you were given a task to create an application to perform intense statistical calculations. You discover that you will use a sorting algorithm in many points of the application. (Okay, we know the .NET Framework has a lot of objects with sorting functionalities, but let's forget about it for a while... ;-) ) You also discover that the effectiveness of the sorting algorithm depends on the number of elements to be sorted. Thus, you create two projects: the Sorting Project, holding different sorting algorithms, and the Sorting Strategy Project, which should test the effectiveness of these algorithms.
These are the sorting algorithm classes to be tested:
BiDirectionalBubbleSort
BubbleSorter
ComboSort11
ComparableComparer
DoubleStorageMergeSort
FastQuickSorter
HeapSort
InPlaceMergeSort
InsertionSort
OddEvenTransportSorter
QuickSorter
QuickSortWithBubbleSort
SelectionSort
ShakerSort
ShearSorter
ShellSort
Since each sorting class implements an ISort
interface with a public Sort
method, we have to make the app.config specify that our around advisor will intercept only the Sort
method of these classes:
<object id="aroundAdvisor"
type="Spring.Aop.Support.NameMatchMethodPointcutAdvisor, Spring.Aop">
<property name="Advice">
<object type="Benchmarking.Aspects.BenchmarkAroundAdvice, Benchmarking" />
</property>
<property name="MappedNames">
<list>
<value>Sort</value>
</list>
</property>
</object>
The Sorting Strategy project has just one form, intended to measure the execution time for the sorting algorithms for different element counts (marks):
You can also click the Copy to Clipboard button and then paste the data directly on an Excel worksheet. After that, you can create a chart to view more clearly the evolution of effectiveness for each sorting algorithm through different element counts:
Conclusion
I hope this simple example may give the readers some background regarding the practical usage of the Aspect Oriented Programming provided by the Spring.NET framework. If you reached this point of the article, thank you very much for your patience. Any comment, advice, or complaint about the article or the examples will be highly appreciated.
History
- 2007/06/13 - Initial posting.
- 2007/07/01 - Code and article updated (Before and After Advices replaced by Around Advice).