locked
Performance of com interop is very slow RRS feed

  • Question

  • We have a VB6 Active X exe that consumes a .net dll.  This .net dll is passed a reference to the Active X exe and then the .net dll carries out work on the objects contained with the Active X exe.

    The active X exe is referenced in the .net assembly using the tlbimp so it can be considered to be early bound.

    This all works great - except the performance is very poor.  To give you an idea, some of this code used to be completed in VbScript and this was FASTER than .net.

    When .net is consumed in this manner are there any settings that need to be set to improve the performance?  As this is a dll consumed by an vb6 exe I did not think there were any special requirements?


    matvdl

    Tuesday, July 9, 2013 6:42 AM

All replies

  • To give you an idea, some of this code used to be completed in VbScript and this was FASTER than .net.

    Maybe you could show Microsoft your test results so they can verify the validity of your statement and then, maybe, see if there is an issue with .Net.


    Please BEWARE that I have NO EXPERIENCE and NO EXPERTISE and probably onset of DEMENTIA which may affect my answers! Also, I've been told by an expert, that when you post an image it clutters up the thread and mysteriously, over time, the link to the image will somehow become "unstable" or something to that effect. :) I can only surmise that is due to Global Warming of the threads.

    • Marked as answer by Youen Zen Monday, July 29, 2013 7:22 AM
    • Unmarked as answer by matvdl Monday, July 29, 2013 7:37 AM
    Tuesday, July 9, 2013 10:27 AM
  • Not sure if I understand your question. If the ActiveX EXE is the client, why would it be consumed by a child component?

    Instead of passing an instance of a COM component I would consider passing less complex parameter types such as strings and numeric data types. This should eliminate some of the the COM interop overhead involved in marshaling the object back and forth.


    Paul ~~~~ Microsoft MVP (Visual Basic)

    Tuesday, July 9, 2013 1:11 PM
  • I have done further testing on this and and created a small applicaiton to demonstrate the results:

    Here is the output of the program

    dotNET EarlyBound execution time was 8.29 ms per 1,000 records
    dotNET LateBound execution time was 11.72 ms per 1,000 records
    VBScript execution time was 2.03 ms per 1,000 records

    As you can see VBScript execution is significantly faster, even though it is all late-bound execution. 4 to 6 times faster!!!

    I would have thought that where something is early bound, dotNET should leave VBScript for dead!!

    To create the test example, you would need to do the following:

    1. Create a new VB6 Active X exe that is standalone operaitons. (Project\Project Properties\Component\Standalone)
    2. Change the startup Object to "Sub main" (Project\Project Properties\General\StartUp Object)
    3. Add a form named MainForm and add a button called RunTest and a lable control to display the results - give the lable control the name lblSpeedTest.
    4. Add a referance to the "Microsoft Script Control 1.0" (Project\Referenses)
    5. Add a new module and add the following code to it
    Dim mainForm As mainForm
    Public Sub Main()
    Set mainForm = New mainForm
    mainForm.Show
    End Sub
    1. Now rename the default class called Class1 to Testobject and add the following code to it 
    Option Explicit
    Public Name As String
    Public Age As Long
    Private Sub Class_Initialize()
    Name = "TestName"
    Age = 38
    End Sub

    2. Now go back to the form and double click the button, within the button OnClick event add the following code

    Private Sub RunSpeedTest_Click()
    Dim Col As New Collection
    Dim Count1 As Long
    Dim STime As Double
    Dim TestObject As Object
    Dim TimeTaken As Double
    Dim Iterations As Long
    'Create the collection of test objects
    Iterations = 100000
    For Count1 = 1 To Iterations
        Col.Add New TestObject
    Next
    'Create the dot net class
    Set TestObject = CreateObject("TestdotNETClient.dotNETClass")
    'Do the early bound test
    STime = Timer
    TestObject.LoopItemsEarlyBound Col
    TimeTaken = Timer - STime
    Me.lblSpeedTest.Caption = "dotNET EarlyBound execution time was " & Round(TimeTaken / Iterations * 1000 * 1000, 3) & " ms per 1,000 records"
    'Do the latebound test
    STime = Timer
    TestObject.LoopItems Col
    TimeTaken = Timer - STime
    Me.lblSpeedTest.Caption = Me.lblSpeedTest.Caption & vbCrLf _
                & "dotNET LateBound execution time was " & Round(TimeTaken / Iterations * 1000 * 1000, 3) & " ms per 1,000 records"
    'Create and use the VBScript control to execute VBScript
    Dim S As New ScriptControl
    S.Language = "VBScript"
    S.AddCode "Public Sub LoopItems(Col)" & vbCrLf _
    & "Dim It" & vbCrLf _
    & "Dim Age" & vbCrLf _
    & "For Each It In Col" & vbCrLf _
    & "Age = It.Age" & vbCrLf _
    & "Next" & vbCrLf _
    & "End Sub" & vbCrLf
    STime = Timer
    S.Run "LoopItems", Col
    TimeTaken = Timer - STime
    Me.lblSpeedTest.Caption = Me.lblSpeedTest.Caption & vbCrLf _
                    & "VBScript execution time was " & Round(TimeTaken / Iterations * 1000 * 1000, 3) & " ms per 1,000 records"
    Debug.Print Me.lblSpeedTest.Caption
    End Sub

    Next is to create the dotNET dll - do the following

    1. Create a new class library
    2. Add a single class and insert the following code in the class
    Public Class dotNETClass
        Public Sub LoopItems(Col As Object)
            Dim Age As Integer
            For Each it In Col
                Age = it.Age
            Next
        End Sub
        Public Sub LoopItemsEarlyBound(Col As VBA.Collection)
            Dim Age As Integer
            Dim It As TestdotNETSeed.TestObject
            For Each It In Col
                Age = It.Age
            Next
        End Sub
    End Class
    1. Now go to referances and add a referance to the VB6 Active exe, you will need to compile it first
    2. Also add a referance to Visual Basic For Applicaitons 6.0 or msvbvm60.dll
    3. Also remember to mark the dotNET dll as make assembly com visible and register for com interop. (under Applicaiton - "Assembly Information" and Compile - "Register for COM Interop"
    4. Also look out in VB6 application for the line "Set TestObject = CreateObject("TestdotNETClient.dotNETClass")" Make sure you have used the correct name based on the dotNET assembly name

    Now it should be ready to run, First compile the VB6 app and then re-compile dot net.  it will need to be done in this order as changes to the VB6 tbl will cause issues in dot NET.  The other option is to mark the VB6 applicaiton as binary compatible.

    Run the VB6 exe and select the button, it will then run the test and place the results in the lable.

    IT is a fairly simple tests - but clerly shows that the performance is very poor for dotNET.  I would have expected that early bound dotNET would be very fast, but here it shows it is not.


    matvdl

    Sunday, July 14, 2013 4:36 AM
  • Does anyone have any comments on the below performance issue that I have raised above, I would be most interested if there is a solution to this problem shown above.

    matvdl

    Tuesday, July 16, 2013 11:40 PM
  • Monkeyboy's response can't be considered a valid answer to this question, at least Paul had a suggestion to try?

    Would be great if there is anyone that may have looked into this issue in the past?  The above tests shows that VBScript out-performs VB.net by a factor of 4-5 times.  That's a significant difference.

    Anyone??


    matvdl

    Monday, July 29, 2013 7:40 AM
  • It seems to me that what you have demonstrated is that COM to COM operations are faster than .NET to COM operations. This is most certainly true, since .NET and COM are not using the same native communication protocol. The only way to reduce the overhead is to reduce the amount of COM interop calls and marshaling.


    Paul ~~~~ Microsoft MVP (Visual Basic)

    Monday, July 29, 2013 4:45 PM
  • Yes - but late bound com has always been known as very slow, this is unbelievably slow.

    Is it possible that I have something like thread switching going on?  The dll is hosted in what I think is a STA apartment I did not think that this could be the case?

    I have also noticed that this appears to be somehow related to the accessing the object for the first time.  As an example If I was to change my code to loop through the collection twice in the dotNET version only, it almost makes no difference to the speed. The second iterations is very very quick.  This means that it has something to do with the first access of the objects in question, anybody know why?


    matvdl

    Monday, July 29, 2013 11:07 PM
  • Hi there,

    I do not know if this issue is still important for you but I have made a very interesting discovery today which may answer your question (I hope). 

    The Problem I have had was actually inside Powershell. I am a big fan of Powershell and .Net and I use quite often late-binding with COM objects (in VB.NET and Powershell directly, in C# with dynamic objects).

    In a small test with a port of VBS solution to powershell, I have realized that the same app in VBS is several times faster (more than 10x). This fact was very dissapointing at first look but it pointed me back to a study I have performed some years ago, when I evaluated VB.NET advantages over C# for COM based applications.

    That time I have used "Reflection" to study how VB.NET implements support for "Late-Binding" (simmilar to old VB), with the purpose to use the same technic inside C# applications (on .Net Framework Versions 1.x and 2.0 I was not able to find native COM Support for Late-Binding).

    The Research pointed me to a class inside "Microsoft.VisualBasic" assembly (by Default referenced inside each VB.NET app), called "Interaction". This class has a nice method called: "CallByName", method which implements the Late-Binding COM-Support inside VB.NET applications. It seems that if you replace your "direct" calls with calls of this function (instead object.Method or object.Property you use Interaction.CallByName(object, 'MethodName', CallType.Method, new object[] {args}) or Interaction.CallByName(object, 'PropertyName', CallType.Get, new object[] {}) you may get better Performance results.

    In my case, my program was up to 30 times faster!!!

    Use this function with COM objects. It seems that by default, the .Net Framework (probably only C# and Powershell) is doing some checks before invoking COM operations, which result is serious Performance "costs". By using the CallByName method you will skip these calls, and you will be much faster.

    Thursday, May 14, 2015 2:41 AM
  • Set on Option Strict On in your VB code for .Net.

    It is really a late binding disaster.


    Success
    Cor

    Thursday, May 14, 2015 11:33 AM
  • Now that the thread is active again anyway...

    I did some tests yesterday, too. In short the results:
    * early bound: 0.9 µs
    * late bound with Option Strict Off: 6.9 µs
    * late bound with MSVB.CallByName: 6.9 µs

    I'm not surprised that the latter two are equal. It's not really important what has been tested, but FWIW:

          Dim con As Object = New ADODB.Connection
          '...
                   MSVB.CallByName(con, "BeginTrans", MSVB.CallType.Method)
                   MSVB.CallByName(con, "RollbackTrans", MSVB.CallType.Method)
                   'con.BeginTrans()
                   'con.RollbackTrans()

    Won't talk about test conditions. They are as exact as technically possible.

    Conclusion: I don't know what made your test show a speed ratio of 30.



    Armin


    Thursday, May 14, 2015 12:03 PM
  • Hi Armin,

    with all the respect, your assumption that "It's not really important what has been tested" is wrong. We are talking here about Com.Interop and you have presented an example with ADO.NET. And more, you should never compare the Speed used in very short operations. At least you can repeat your test 1 thousand times.

    For example, my app is a port of an old VBS script (to PowerShell) which will iterate through all open Internet Explorer - Windows and collect some Informations about the WebBrowser control (Title, Url, ...):

    function Initialize-MSVBWrapper {
        [CmdletBinding()]
        param()
        if (-not $script:msvb_wrapper) {
            [void][reflection.assembly]::LoadWithPartialName("Microsoft.VisualBasic")
            $script:msvb_wrapper = $true
        }
    }
    
    function Get-IEWindows {
        [CmdletBinding()]
        param([switch]$Direct)
        
        Initialize-MSVBWrapper
        $private:objShell = New-Object -ComObject "Shell.Application"
        try {
            if ($Direct) {
                $private:objAllWindows = $private:objShell.Windows()
            } else {
                $private:objAllWindows = [Microsoft.VisualBasic.Interaction]::CallByName($private:objShell, 'Windows', 'Method', @())
            }
            foreach ($private:objWindow in $private:objAllWindows) {
                try {
                    if ($Direct) {
                        $private:toplevel = $private:objWindow.TopLevelContainer
                    } else {
                        $private:toplevel = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'TopLevelContainer', 'Get', @())
                    }
                    if ($private:toplevel) {
                        if ($Direct) {
                            $private:fullname = $private:objWindow.FullName
                            $private:location = $private:objWindow.LocationName
                            $private:url      = $private:objWindow.LocationURL
                            $private:visible  = $private:objWindow.Visible
                            $private:hwnd     = $private:objWindow.HWND
                        } else {
                            $private:fullname = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'FullName',     'Get', @())
                            $private:location = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'LocationName', 'Get', @())
                            $private:url      = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'LocationURL',  'Get', @())
                            $private:visible  = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'Visible',      'Get', @())
                            $private:hwnd     = [Microsoft.VisualBasic.Interaction]::CallByName($private:objWindow, 'HWND',         'Get', @())
                        }
                        
                        New-Object PSObject |
                        Add-Member -PassThru -MemberType NoteProperty -Name "FullName" -Value $private:fullname |
                        Add-Member -PassThru -MemberType NoteProperty -Name "Location" -Value $private:location |
                        Add-Member -PassThru -MemberType NoteProperty -Name "Url"      -Value $private:url      |
                        Add-Member -PassThru -MemberType NoteProperty -Name "Visible"  -Value $private:Visible  |
                        Add-Member -PassThru -MemberType NoteProperty -Name "PID"      -Value $private:hwnd
                    }
                } finally {
                    [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($private:objWindow)
                }
            }
        } finally {
            [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($private:objShell)
            $private:objShell = $null
        }
    }
    
    
    Initialize-MSVBWrapper
    
    $time = [DateTime]::Now
    Get-IEWindows -Direct | Out-Null
    $duration = [DateTime]::Now - $time
    Write-Host "Direct com-interop: $duration"
    
    Write-Host 
    
    $time = [DateTime]::Now
    Get-IEWindows | Out-Null
    $duration = [DateTime]::Now - $time
    Write-Host "VB-like com-interop: $duration"
    
    Write-Host 
    
    $time = [DateTime]::Now
    Get-IEWindows -Direct | Out-Null
    $duration = [DateTime]::Now - $time
    Write-Host "Direct com-interop: $duration"
    
    

    Depending on how many Web-Pages you have open (better is to open as many URLs as possible), you will see bigger and bigger time differences.

    Here are some results (with 13 Windows):

    Direct com-interop: 00:00:09.7546343

    VB-like com-interop: 00:00:00.2689960

    Direct com-interop: 00:00:08.8325097

    # I have Internet Explorer 11.

    You can see here that using Interaction.CallByName is much better.

    Friday, May 15, 2015 10:51 AM
  • with all the respect, your assumption that "It's not really important what has been tested" is wrong.

    I meant that, if you test a call, the speed of the call is of interest, not what has been implemented inside the called function.

    "you have presented an example with ADO.NET"

    No, I'm using ADOB via COM Interop.

    "you should never compare..."

    I said my test is as reliable as technically possible. If you don't believe it, we don't have a base for a discussion.

    (As I got tired of writing performance tests again and again for this and that situation, I developed a performance test class and a client application that is easy to use, and tests can easily be added. During tests, I've also been debugging native code to verify what's going on, in particular, that no code is optimized away. The only overhead is the one of the loop surrounding the test (see code). The project is started without a debugger attached. Every test is run multiple times to verify the result is plausible and JIT compilation is not an issue. The CPU spins up before the actual test to avoid deviations for this reason. There was the option to run the test with maximum thread and process priority (which can render the machine unusable!) but apart from the additional danger, it didn't make a difference, so I don't make use of it, mainly because influence from other running processes should be excluded as good as possible anyway.)

    This is the test routine:

       Public Shared Sub TestCOMLateBound(ByVal count As Integer, ByRef StartTime As Long, ByRef EndTime As Long)
    
          Dim con As ADODB.Connection = New ADODB.Connection
          Try
             con.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\Users\Armin\Desktop\tmp.mdb")
             Try
                StartTime = Stopwatch.GetTimestamp
    
                For i As Integer = 1 To count
                   'MSVB.CallByName(con, "BeginTrans", MSVB.CallType.Method)
                   'MSVB.CallByName(con, "RollbackTrans", MSVB.CallType.Method)
                   con.BeginTrans()
                   con.RollbackTrans()
                Next
    
                'early bound: 1 µs
                'late bound: 6.5 µs
                'MSVB.CallbyName: 6.9 µs
    
                EndTime = Stopwatch.GetTimestamp
    
             Finally
                con.Close()
             End Try
          Finally
             Runtime.InteropServices.Marshal.ReleaseComObject(con)
             con = Nothing
          End Try
    
       End Sub


    This function is utilized by the performance test class, and the measured time contains only the loop.

    This is the result (at the bottom left) for the early-bound version (1 mio iterations). I've modified the routine for the three different test.

    Late-bound (Option Strict Off):

    Call by name:

    [follows in the next message because of image limitation of 2]

    You did not provide any VB.Net code that compares VB.Net's late-binding to VB.Net's CallByName function. You've shown a complex example that I'm not able to analyze. As far as I can see, your test is not limitted to test the speed of one function call.


    Armin


    Friday, May 15, 2015 11:38 AM
  • Follow up, missing image: (Call by name)


    Armin

    Friday, May 15, 2015 11:38 AM
  • Hi Armin,

    with all the respect, your assumption that "It's not really important what has been tested" is wrong. We are talking here about Com.Interop and you have presented an example with ADO.NET. And more, you should never compare the Speed used in very short operations. At least you can repeat your test 1 thousand times.


    Armin did not test with Ado.Net but with old ADO. That is not .Net but Com. Beside that I can assure you that Armin does not test one time but thousand times, he knows that already for more than a decade. 




    Success
    Cor

    Friday, May 15, 2015 11:43 AM
  • Hi Guys,

    please do not loose your patience. There are some issues that are not clear here and we need to set things right.

    Armin, according to my Research, for you there should be no Performance difference because you already use VB.NET which is in my oppinion optimized for COM interop (quicker you will never get).

    If I am right, using Late-Binding in VB.NET should translate the statement "obj.Method" into "Interaction.CallByName(obj, "Method", CallType.Method)". This means that the result (execution time) should be in both your test cases the same, because the compilation should produce the same executable code. Probably this is the issue you are having.

    I on the other side, I was comparing VBS (Visual Basic Script) and PowerShell. And IMHO, the thread here does not compare VB.NET alternatives for Late-Binding, here we compare old VB with .Net code (in my case, PowerShell). And in my sample with PowerShell, the Speed difference is really a big issue. Using "Interaction" static object in PowerShell, I was able to speed up my PowerShell implementation, to relatively match the speed I was having with VBS.

    I will soon evaluate this issue using Late-Binging implemented with dynamic objects in C#.


                  

    Friday, May 15, 2015 5:15 PM
  • Marian,

    right, this is a different situation.

    I don't think that the compiler supported late-binding will be translated to calls to CallByName[*], but in the end, reflection is used in both cases, therefore there's not much of a difference. I admit that there might be a bigger difference if arguments have to be passed.

    Probably I misread your post (even though I read it twice), maybe because I didn't expect that comparing Powershell or VBS to CallByName is of interest to this forum. I mean, if you say, "use CallByName because it is faster", then I don't expect it is being compared to Powershell or VBS because you would have to address this to the PS/VBS developers then. For VB developers it's not faster. Just to explain my line of thoughts. As this thread was a couple of days old, I didn't read it completely, only the title about the performance of COM Interop.

    Anyway, thanks for clarifying.

    ...

    [*] This (with Option Strict Off)...

          Dim o As Object = New Form
          o.show()

    ...is compiled to this

      IL_0018:  call       object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateCall(object,
                                                                                                                         class [mscorlib]System.Type,
                                                                                                                         string,
                                                                                                                         object[],
                                                                                                                         string[],
                                                                                                                         class [mscorlib]System.Type[],
                                                                                                                         bool[],
                                                                                                                         bool)


    Armin

    Friday, May 15, 2015 5:39 PM
  • Hi Armin,

    you already decompiled the app :-). This should be the up-to-date Information, as my Research was made some years ago with older .Net Framework (Version 2.0).

    If you look in Interaction implementation with ILSpy (does not matter which Framework Version) you will see that it uses internally another class (in the same Namespace) called "LateBinding".

    The Name now of the Class Points to an optimization, it seems that MS use now NewLateBinding instead of LateBinding class. Probably, the old Name is only for backward compatibility.

    Friday, May 15, 2015 5:57 PM
  • Hi There,

    I have completed several different tests with COM and Late-Binding. I have tested VBScript against Powershell, C# and VB.NET application, with or without my optimization, in interaction with Microsoft Excel. From what I can say, only Powershell and Late-Binding is extremly slow when COM Methods are referenced directly ($object.Method). And when I say extremly, I am very serios, the speed decrease exponentially with each extra "Late-Binding" call.

    Otherwise the Speed is comparable the same between all other cases. For normal and relativly few COM operations, the VBS script seems to be faster. But when the number of COM operations increase, the Performance gain turns slowly toward .Net. But the differences were minimal in my case.

    Even when Powershell is used with "Interaction.CallByName" function (as previously mentioned), the implementation is comparable with the one made in VB.NET.

    This means that the PowerShell without optimization is the single case where I can confirm a performance problem. Otherwise, I cannot confirm any other Problem in managed code. ... Though, it seems that the Performance of managed implementations are more directly dependent on CPU loads.

    Wednesday, May 20, 2015 11:03 PM