Steve
Steve

Reputation: 117

Why is C# much faster at calling a function in an AppDomain than VB

I have created an AppDomain sandbox to allow users to run their own code (C# or VB) from a parent application (written in VB). I extracted the essential code and created two identical applications, one in VB one C#.

I was astonished to find that the C# version runs at least 60 times faster.

I cannot find any references to this behaviour on StackOverflow or Google. Is there some major inefficiency in the way VB serialises the Invoke call?

Here is the VB code that is the type being executed:

Imports System.Reflection

Namespace UserCode
  Namespace Runtime

    Public Class Execute
      Inherits MarshalByRefObject

      Private _MethodInfo As MethodInfo

      Sub New()

        _MethodInfo = Nothing

      End Sub

      Public Sub SetAssembly(assemblyName As String, functionName As String)

        _MethodInfo = Nothing

        If assemblyName <> "" Then

          Dim assembly As Assembly = AppDomain.CurrentDomain.Load(assemblyName)
          Dim type As Type = assembly.GetType("CompiledUserCode")

          _MethodInfo = type.GetMethod(functionName, BindingFlags.Public Or BindingFlags.Static)

        End If

      End Sub

      Public Function ExecuteFunction(args() As Object) As Object

        Return _MethodInfo.Invoke(Nothing, args)

      End Function

    End Class

  End Namespace
End Namespace

Here is the equivalent C#

using System;
using System.Reflection;


namespace UserCode
{
   public class Execute:MarshalByRefObject
    {
        private MethodInfo _MethodInfo;

     public Execute()
      {
        _MethodInfo = null;

      }

      public void SetAssembly(string assemblyName ,string functionName)
      {
        _MethodInfo = null;

        if( assemblyName != "")
        {

         var assembly  = AppDomain.CurrentDomain.Load(assemblyName);
          var type = assembly.GetType("CompiledUserCode");

          _MethodInfo = type.GetMethod(functionName, BindingFlags.Public | BindingFlags.Static);

        }
      }

      public object ExecuteFunction(object[] args)
      {
          return _MethodInfo.Invoke(this, args);
      }
    }
}

Here is the VB IL (Constructor + Execute):

.class public auto ansi UserCode.Runtime.Execute
    extends [mscorlib]System.MarshalByRefObject
{
    // Fields
    .field private class [mscorlib]System.Reflection.MethodInfo _MethodInfo

    // Methods
    .method public specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x21c0
        // Code size 14 (0xe)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.MarshalByRefObject::.ctor()
        IL_0006: ldarg.0
        IL_0007: ldnull
        IL_0008: stfld class [mscorlib]System.Reflection.MethodInfo UserCode.Runtime.Execute::_MethodInfo
        IL_000d: ret
    } // end of method Execute::.ctor

.method public 
    instance object ExecuteFunction (
        object[] args
    ) cil managed 
{
    // Method begins at RVA 0x221c
    // Code size 14 (0xe)
    .maxstack 3
    .locals init (
        [0] object ExecuteFunction
    )

    IL_0000: ldarg.0
    IL_0001: ldfld class [mscorlib]System.Reflection.MethodInfo UserCode.Runtime.Execute::_MethodInfo
    IL_0006: ldnull
    IL_0007: ldarg.1
    IL_0008: callvirt instance object [mscorlib]System.Reflection.MethodBase::Invoke(object, object[])
    IL_000d: ret
} // end of method Execute::ExecuteFunction

Here is the C# IL:

.class public auto ansi beforefieldinit UserCode.Execute
    extends [mscorlib]System.MarshalByRefObject
{
    // Fields
    .field private class [mscorlib]System.Reflection.MethodInfo _MethodInfo

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x275c
        // Code size 14 (0xe)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.MarshalByRefObject::.ctor()
        IL_0006: ldarg.0
        IL_0007: ldnull
        IL_0008: stfld class [mscorlib]System.Reflection.MethodInfo UserCode.Execute::_MethodInfo
        IL_000d: ret
    } // end of method Execute::.ctor

.method public hidebysig 
        instance object ExecuteFunction (
            object[] args
        ) cil managed 
    {
        // Method begins at RVA 0x27b4
        // Code size 14 (0xe)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld class [mscorlib]System.Reflection.MethodInfo UserCode.Execute::_MethodInfo
        IL_0006: ldarg.0
        IL_0007: ldarg.1
        IL_0008: callvirt instance object [mscorlib]System.Reflection.MethodBase::Invoke(object, object[])
        IL_000d: ret
    } // end of method Execute::ExecuteFunction

The only significant difference I can see is:

.maxstack 3
        .locals init (
            [0] object ExecuteFunction
        )

At present if I wish to take advantage of the C# speed my only option is to create a separate C# assembly containing the sandbox code. The 60 times is actually an under-estimate. It is measured simply by calling the ExecuteFunction function with:

object[] objects = new object[] { 1.0, 1.0, 1.0 };

as the arguments and varying object[0] to prevent any optimisations (100000 loops).

The code that actually gets run in the sandbox is very simple:

public static double StressFactor(double useStress, double stress, double p)
           {
           return useStress*stress+p;
           }

On re-measuring the speed difference is closer to 41 times faster in C#.

Upvotes: 3

Views: 274

Answers (1)

Steve
Steve

Reputation: 117

After (quite) a few hours investigating this I believe that the issue is caused by the extra 'decoration' that VB gives to classes behind the scenes.

If you check out a simple class in VB it has quite a few more properties etc than the equivalent C# class (eg Binder... / Declared...). This even applies to C# classes instantiated in VB. In addition the performance analyzer showed that deserializing/serializing ClaimsIdentity was taking a goodly proportion of the time in VB. No sign of this in C#. Again I am guessing this is the extra 'decoration' given to a class in VB.

Checked around a bit and can see no way to remove this extra stuff.

So my only solution is to implement the sandbox code in a separate C# dll.

Upvotes: 2

Related Questions