Thomas Alva Edison's light bulb VBInfoZine Home
   An ordinary VB developer shares his own successes and failures
   FREE to registered subscribers.  

.NET Interception (2nd part)

Discusses yet another interception technique - extending the RealProxy. Compares the two interception techniques (channel sinks and RealProxy) from a performance standpoint.

After having written the ".NET Framework Interception" article I came through another interception technique that seemed to be much simpler to implement and possibly more efficient at the same time - extending the RealProxy.

This time, however, I didn't have any real project for testing the RealProxy interception technique. Because I was still very curious about the performance characteristics of the various interception "machineries" available in the framework, I decided to write a test application that will provide data to help me and you assess the performance overhead of the two interception techniques.

The LaMarvin.Interception.Test.exe application was born.

The application allows comparing the relative performance of the following method invocation techniques:

  1. Direct call. This means calling instance methods without crossing any .NET framework remoting boundary. This is presumably the most efficient invocation technique.
  2. Direct call with Reflection. The same as direct call but using reflection to invoke methods.
  3. RealProxy interception. Calls methods of a class within the same AppDomain with a RealProxy-based interception. The interception code does nothing else than forwarding the call to the proxied object.
  4. Server context sink interception. Calls methods of a class within the same AppDomain with a server context sink. The sink does no processing except forwarding a call to the next sink in the context's sink chain.
In order to compare the relative overhead of method argument marshaling, two methods are called - one method without arguments and one method with both a value-typed and a reference-typed argument:
Public Sub MethodWithoutArgs()

Public Sub MethodWithArgs( _
  ByVal i As Integer, ByVal s As String)
Because the interception techniques in .NET framework are based on derivation, the methods are defined on the following three classes:
' For direct method invocation:
Public Class TestClassDirect

' For RealProxy interception:
Public Class TestClassMbr
 Inherits MarshalByRefObject

' For server sink interception:
Public Class TestClassContextBound
 Inherits ContextBoundObject
In order to make the testing easily extensible, I've defined an abstract TestBase class with one Run method:
Public MustInherit Class TestBase
  Public MustOverride Sub Run()
End Class
The TestBase.Run method is overriden in TestBase's descendants to call the appropriate method of an appropriate TestClassXXXX instance (created upon construction), for example:
Public Class TestMethodWithoutArgsDirect
  Inherits TestBase
  Private _Target As New TestClassDirect
  Public Overrides Sub Run()
    _Target.MethodWithoutArgs()
  End Sub
End Class
The actual duration of the method calls is measured within a TestDriver class, which manages a typed collection of TestBase descendants and collects and stores the actual test results.

The tests itself are run on a separate thread taking advantage of the .NET framework's thread pool service and illustrating some of the aspects of multithreaded UI programming, such as synchronization and locking.

The complete solution can be downloaded at http://www.vbinfozine.com/c/interceptest.zip

Now take a look at the results from a sample test run with 10000 method invocations:

Test name Number of iterations Method call duration [ms]
WithoutArgsDirect 10000 18
WithArgsDirect 10000 18
WithoutArgsReflection 10000 32
WithArgsReflection 10000 54
WithoutArgsProxy 10000 455
WithArgsProxy 10000 536
WithoutArgsContext 10000 436
WithArgsContext 10000 429

The results show that Reflection can be from 0.8 to 3 times slower than direct invocation, depending largely on the number and type of method arguments.

The overhead of the interception techniques (RealProxy and server context sink) is similar...and it is tremendous. It can be as much as 30 times slower than direct method invocation!

Knowing this, I certainly wouldn't have tried to use interception to implement the access control feature discussed in the previous article ;-).

The conclusions from the article still apply, IMHO:

.NET framework interception is a great technique provided the associated performance penalty is negligible. For remotely accessed objects, this is a "no-brainer", because the interception infrastructure is already established and used anyway.

Implementation note

While implementing the TestDriver class I had to solve one interesting problem:

I've designed the TestDriver class to raise a TestStarted event when the tests begin, and a TestFinished event, when the tests are done. Because the tests are run on a worker thread, the TestFinished event cannot be raised directly, because the event handling UI code would end up running on a worker thread.

Because I didn't want to force the TestFinished event handling code to use Control.Invoke, I had to emulate the Control.Invoke's functionality to "marshal" the event back to the UI thread somehow.

I've used the great Lutz Roeder's .NET Reflector to try to decompile the Control.Invoke code and look at how they do the marshaling. Unfortunately, I've got an exception "Member 'System.Threading.CompressedStack.GetCompressedStack' cannot be resolved".

Because my TestDriver class runs within a Winforms application, I've decided to use an instance of the System.Windows.Forms.Timer class to poll for a "finished" flag, which is set by the worker thread when it finishes the tests. The Timer.Tick event handling code (running always on the main thread by definition) checks the "finished" flag and if it is set, it raises the TestFinished event.

This way, the TestFinished event handling code won't have to be concerned about which thread raised the event - it will always run on the main (UI) thread and the code can update the controls directly without using Control.Invoke.

For details, see the TestDriver.vb source file in the accompanying solution.

© Palo Mraz, Tuesday, September 16, 2003

Links

Extending the RealProxy - Doug Purdy's 10 minutes speech about extending the RealProxy - my inspiration for this week's article. Even my poor Slovak ears could understand that stuff - great!

Create a Custom Marshaling Implementation Using .NET Remoting and COM Interop - MSDN Magazine article. The sample code is a bit artificial and the writing style is a bit confusing. Not my favorite one.

MS KB article "HOW TO: Use QueryPerformanceCounter to Time Code in Visual Basic .NET".

"Extending RealProxy" from the .NET Developer's Guide.

 ©2003-2007 Palo Mraz. All Rights Reserved.   See my 'new browser window' policy