Tuesday, January 08, 2008

JScript.NET performance

JScript.NET doesn't really seem to have caught on too much. The reasons are clear, there is no IDE support for it in Visual Studio.NET and if the company that developed the language doesn't want to provide an IDE, who else is likely to?

But since Metastorm BPM uses JScript.NET as its primary interface to the world of .NET I have to use it on a fairly regular basis. Many moons ago I had a look at the IL code produced from compiling JScript.NET and was horrified to see it was all late bound calls. Funnily enough the primary reason somebody had decided to use JScript.NET rather than JScript was to try to improve the performance of 8000 lines of server-side JScript code (why he had ever thought that 8000 lines of JScript code would not have performance problems I don't know). Anyway, compiling it did improve the performance somewhat but probably not so much as if the IL code produced had not been late bound.

So now several years later, whilst preparing some course notes, I thought I'd take a look at what the IL code looked like these days in .NET 2. And the results are worth noting I think.

Say we have two methods as follows

        public static function DoSomethingFast() : Object
            var test = new StringBuilder();

            return test.ToString();

        public static function DoSomethingSlow() : Object
            var test = "Hello world";

            var test = new StringBuilder();

            return test.ToString();

After compiling that with jsc.exe, the IL produced for the first method looks like this

.method public static object DoSomethingFast() cil managed
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Text.StringBuilder builder,
        [1] object obj2)
    L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0005: stloc.0
    L_0006: ldloc.0
    L_0007: ldstr "a"
    L_000c: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0011: pop
    L_0012: ldloc.0
    L_0013: ldstr "a"
    L_0018: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_001d: pop
    L_001e: ldloc.0
    L_001f: callvirt instance string [mscorlib]System.Text.StringBuilder::ToString()
    L_0024: stloc.1
    L_0025: br L_002a
    L_002a: ldloc.1
    L_002b: ret

Looking at that lifted my spirits since it looks like the kind of code you'd get from a compiled C# method, no late-binding at all. Now on to the second method, which has only one difference from the first one.

.method public static object DoSomethingSlow() cil managed
    .maxstack 11
    .locals init (
        [0] object obj2,
        [1] object obj3,
        [2] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding,
        [3] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding2,
        [4] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding3,
        [5] object obj4,
        [6] object obj5,
        [7] object obj6)
    L_0000: ldstr "Append"
    L_0005: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_000a: stloc.2
    L_000b: ldstr "Append"
    L_0010: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0015: stloc.3
    L_0016: ldstr "ToString"
    L_001b: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0020: stloc.s binding3
    L_0022: ldstr "Hello world"
    L_0027: stloc.0
    L_0028: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_002d: stloc.0
    L_002e: ldloc.2
    L_002f: dup
    L_0030: ldloc.0
    L_0031: ldtoken TestSpeed.TestClass
    L_0036: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_003b: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0040: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_0045: ldc.i4.1
    L_0046: newarr object
    L_004b: dup
    L_004c: ldc.i4.0
    L_004d: ldstr "a"
    L_0052: stelem.ref
    L_0053: ldc.i4.0
    L_0054: ldc.i4.0
    L_0055: ldtoken TestSpeed.TestClass
    L_005a: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_005f: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0064: pop
    L_0065: ldloc.3
    L_0066: dup
    L_0067: ldloc.0
    L_0068: ldtoken TestSpeed.TestClass
    L_006d: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0072: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0077: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_007c: ldc.i4.1
    L_007d: newarr object
    L_0082: dup
    L_0083: ldc.i4.0
    L_0084: ldstr "a"
    L_0089: stelem.ref
    L_008a: ldc.i4.0
    L_008b: ldc.i4.0
    L_008c: ldtoken TestSpeed.TestClass
    L_0091: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0096: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_009b: pop
    L_009c: ldloc.0
    L_009d: stloc.s obj4
    L_009f: ldloc.s obj4
    L_00a1: isinst object
    L_00a6: dup
    L_00a7: stloc.s obj5
    L_00a9: brfalse L_00ba
    L_00ae: ldloc.s obj5
    L_00b0: callvirt instance string [mscorlib]System.Object::ToString()
    L_00b5: br L_00ee
    L_00ba: ldloc.s obj4
    L_00bc: stloc.s obj6
    L_00be: ldloc.s binding3
    L_00c0: dup
    L_00c1: ldloc.s obj6
    L_00c3: ldtoken TestSpeed.TestClass
    L_00c8: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00cd: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00d2: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_00d7: ldc.i4.0
    L_00d8: newarr object
    L_00dd: ldc.i4.0
    L_00de: ldc.i4.0
    L_00df: ldtoken TestSpeed.TestClass
    L_00e4: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00e9: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00ee: stloc.1
    L_00ef: br L_00f4
    L_00f4: ldloc.1
    L_00f5: ret

Suddenly things look terribly messy again, with lots of late-binding goo all over the place. The reason is pretty straight forward I think. In the first case, the compiler can infer the type of the test variable, whereas in the second case it can't because the variable is used to hold two different types. It does however mean that minor changes to code can lead to it being compiled completely differently without any warning, which is a bit of a worry.

No comments: