About Performance of the Policy Injection Block

Topics: General discussion, Policy Injection Application Block
Mar 5, 2007 at 8:13 PM
Here are some though related to the performance of the Policy Injection block. I know it has been discussed on Tom's and Ed's blogs and I have had myself some words with Olaf.

Some have already started to put the Policy Injection block on the benchmark and have measured big differences in performance between a 'normal' method and an 'intercepted' one. I think it is unfair. First, this is a very first release and, looking at the source code, it's clear that performance has not been optimized. Also, it is necessary to make the difference between the following 'costs' of intercepting a method:

1. The time it takes to apply matching rules and to build the pipeline of handlers. This is very expensive. But it is now done on each method invocation, and could obviously done only the first time the method is called. This is a trivial optimization, of course, and I don't doubt it will be done in the next release.

2. The time taken by the interception mechanism. Remoting proxies are not for free. The project PostSharp4EntLib (http://www.postsharp.org/projects/PostSharp4EntLib) generates interception stubs at compile-time, which makes proxies useless. But even if these stubs are much faster than proxies, they are still much slower than normal method invocation without stubs. One of the reasons is that all arguments should be boxed and passed as an array of objects, which makes the 'injected' code GC-unfriendly. Another reason is that we use dynamic invocation mechanism (delegate invocation for PostSharp4EntLib, reflection invocation for remoting proxies). So the JIT compiler cannot optimize across method boundaries. So even if the pipeline of call handlers were empty, using the interception mechanism has a non-trivial cost.

3. The time taken by the call handlers themselves. These call handlers encapsulates other application blocks of the Enterprise Library. Did you look at their design? They are optimized for abstraction and scope, not for performance (I don't say that performance is neglected, I say that it is not the first concern). Here also, much optimization work can and will be done, like initializing the call handler only the first time the method is invoked. But it will never be that fast.

Olaf once asked what performance gain could be achieved by 'inlining' the call handlers at compile-time, i.e. by generating instructions to call every call handler individually. Well, look at the current design of the call handlers and remember my three costs. Since matching would be performed at compile time, we would not do it at runtime. But that's all. The need for interception and boxing all arguments remains. We still have to use dynamic calls. And, of course, we still want to call the handlers.

This leads me to the conclusion that 'Policy Injection' will always be costly. Performance is not a first class argument for compile-time processing in most scenarios. Policies should be injected on coarse grains APIes or on time consuming ones (like database access, web service invocation, ...). The major advantage of policy injection is that it is definable at runtime and this has obviously a cost.

For scenarios where more performance is required and where 'policies' (let call them aspects in this case) are known are compile-time, a different approach than Composition Filters may be preferable. This approach would be implement each 'aspect' (logging, security, exception handling) as a class that would instruct the post-compiler to modify the code. These classes would implement methods like OnEntry, OnExit, OnSuccess, OnException, which can be called from the refactored custom code. So these aspects could be really inlined in the code. Static calls could be used. And brave programmers could even avoid to box all arguments. For instance, an exception handling aspect would add an exception handler around the target method. The method would not be slowed down unless it throws an exception.

This approach is the one of PostSharp Laos and some examples are available online at http://doc.postsharp.org/UserGuide/Samples/Overview.html. I would like to implement (or to help implementing) such custom attributes for the Enterprise Library. I repeat, these custom attributes would be for different scenarios than Policy Injection.

I hope these considerations have somewhat enlightened the discussion about performance of policy injection and the eventual benefits of compile-time processing.

Gael



Mar 5, 2007 at 9:17 PM
Good stuff, Gael.

I am relatively new to AOP, and the Policy Injection Application Block has really opened my eyes to the possibilities.

Correct me if I am wrong, but you are suggesting that there are performance improvements to be gained from post-compiler alternatives when behavior ( handler calls ) is essentially known at compile-time. Using a post-compiler solution that injects known behavior into our classes, one could eliminate the process of determining if a condition matches a rule and immediately call those methods injected into our code. That's a cool idea.

The only drawback I see with that scenario is that now I can't change those rules / behavior via a configuration file at run-time because the code has been physically injected into the IL during compilation. To change the rules and behavior with a post-compiler approach I would need to re-compile and re-publish the code.

One of the nice things I like about Enterprise Library and the PIAB is that I not only have the abstraction, but also the ability through configuration at runtime to change behavior without forcing me to change the source code and re-compile. As you suggest, however, this may come at the price of a performance hit compared to post-compiler alternatives. I also don't need the ability to change behavior at run-time for everything.

Patterns and Practices has always been really good about their documentation, so I expect their guidance will reflect when PIAB is appropriate and not appropriate and talk more about any performance implications and the performance optimizations they have made in the block.

Your comments bring up good food for thought and I look forward to any further thoughts you have on this topic and anyway you can improve the use of AOP via Enterprise Library. I love this stuff!

Regards,

Dave

_____________________

David Hayden
Microsoft MVP C#
Mar 6, 2007 at 5:37 AM
Edited Mar 6, 2007 at 6:05 AM
Yes, that's exactly what I said. To be briefer:
- Performance gains expected from post-compilation are minor. Post-compilation brings the benefits of not being dependent on MBRO or interface-for-everything. So I suggest not to introduce in the discussion performance as a benefit of compile-time weaving for the Policy Injection block.
- Matching rules at compile-time is for another scenario. And for this other scenario, the approach of CF seems to be suboptimal. I will provide a sample so you can judge the difference.

Mar 6, 2007 at 7:18 AM
Edited Mar 6, 2007 at 12:27 PM
I have taken a peek on your postsharp, Gael, and i looks fine.

I'm looking forward to see the sample.
Mar 6, 2007 at 1:11 PM
Edited Mar 6, 2007 at 1:12 PM
I have created a sample custom attribute that injects an Exception Policy into a method, but without the Policy Injection block. The code can be downloaded from http://postsharp.sourceforge.net/PostSharp4EntLib. The class is named ExceptionPolicyAttribute.

First look at the C# code and compare the methods BankAccount.Close and CustomerProcesses.Remove.

InjectPolicy("Exception Handling")
public void /*BankAccount::*/Close() { ... }

ExceptionPolicy("Business Process Policy")
public void /*BusinessProcesses::*/ Remove( Customer customer )
{
foreach (BankAccount account in customer.BankAccounts)
{
account.Close();
}
}


In both methods, the "Business Process Policy" exception handling policy had been applied. In BankAccount.Close, the Policy Injection block has been used and a stub will be generated at compile-time. In CustomerProcesses.Remove, we used the ExceptionPolicy custom attribute, whose implementation looks like this:

public sealed class ExceptionPolicyAttribute : OnExceptionAspect
{
private string exceptionPolicyName;

public ExceptionPolicyAttribute(string exceptionPolicyName)
{
this.exceptionPolicyName = exceptionPolicyName;
}

public override void OnException(MethodExecutionEventArgs eventArgs)
{
Exception exceptionToThrow;
bool rethrow = ExceptionPolicy.HandleException(eventArgs.Exception,
this.exceptionPolicyName, out exceptionToThrow);
if (rethrow)
{
if (exceptionToThrow != null)
{
throw exceptionToThrow;
}
else
{
eventArgs.FlowBehavior = FlowBehavior.RethrowException;
}
}
else
{
eventArgs.FlowBehavior = FlowBehavior.Continue;
}
}
}
}

Now build the project. If PostSharp has been correctly installed, the assembly will be post-processed after compilation. Open Lutz Roeder's Reflector and look at our methods BankAccount.Close and CustomerProcesses.Remove.


public void /*BankAccount::*/ Close()
{
Delegate delegate2 = new ~PostSharp~Laos~Implementation.~delegate~1(this.~Close);
MethodInvocationEventArgs eventArgs = new MethodInvocationEventArgs(delegate2, null);
~PostSharp~Laos~Implementation.~aspect~11.OnInvocation(eventArgs);
}

The first method has been completely replaced by a stub. The old implementation has been moved to the method ~Close. A delegate to this method is created and passed to the method OnInvocation of the class PolicyInjectionAdvice, which calls the PIAB pipeline.

Now look at the second method:

public static void /*CustomerProcesses::*/ Remove(Customer customer)
{
try
{
foreach (BankAccount account in customer.BankAccounts)
{
account.Close();
}
}
catch (Exception exception)
{
object[] arguments = new object[] { customer };
MethodExecutionEventArgs eventArgs = new MethodExecutionEventArgs(methodof(CustomerProcesses.Remove, CustomerProcesses), null, arguments);
eventArgs.Exception = exception;
~PostSharp~Laos~Implementation.~aspect~5.OnException(eventArgs);
switch (eventArgs.FlowBehavior)
{
case FlowBehavior.Continue:
case FlowBehavior.Return:
return;
}
throw;
}
}

The difference should be striking: no stub has been generated and the exception handling stuff has been 'inlined' inside the original method. The exception handling causes a performance overhead only when an exception flies. The exception handler calls the OnException method of the ExceptionPolicyAttribute class.

This was to illustrate the difference between Composition Filters, which models a method invocation as a request-reply messages pair going through a pipeline of filters, and 'joinpoint-based' or 'code-event-based' aspects, which does not abstract anything but provide a way to modify the source or intermediate code. Composition Filters are more usable for some scenarios, but cannot be 'inlined' in code.

It will be interesting, after the final release of PIAB will be published, to compare performances of different implementation and to discuss what is best for different scenarios.

Gael