hexagon logo

CMM Automation Speed C# versus VB6

Hi,

I am currently converting an application that automated PC-DMIS in VB6 over to C#. Our application does the following:


1) Ask the user what PC-DMIS program (i.e. a PRG file) they want to run
2) Launch PC-DMIS passing in the name of the program file selected
3) Get PC-DMIS to run a series of measurements on a Scirocco machine
4) Import the measurements from PC-DMIS into the application for processing
5) Process the results (basically we compare the measurements to a baseline, then insert the deviation into a database)

What I observed is that for any program (we've tried quite a few and this seems to be consistently happening), the VB6 application takes approximately 30 seconds to perform step 4, whereas the C# application takes around 2.5 minutes.

Can anyone help with the following questions please?

1) We directly ported the code from the legacy VB6 application to C# so the new program isn't doing anything different from what the old VB6 one used to do, so why does C# take so much longer than VB6 to perform the import?
2) Is there any way to speed it up, e.g. I think I read something on a forum that VB.NET may be faster, but I can't find anything technical from Microsoft to support that so I can't really justify burning a lot of hours to convert from C# to VB.NET in case it is the same speed?

Thanks!
  • I don't know the answer, but I do have a comment/question. I understand your hesitation to convert from C# to VB.Net, but the conversion should go much faster if you convert the old VB6 into VB.Net. There are tools to make that transition somewhat painless.
  • ...the conversion should go much faster if you convert the old VB6 into VB.Net. There are tools to make that transition somewhat painless.


    +1

    This might speed up the import too.
  • If the conversion from VB6 is being done correctly, there should be little if any performance difference between a C# or VB.NET application. At the end of the day, either language will compile into identical MSIL so long as the code is functionally the same. I would recommend taking a closer look at the methods being called in the offending part of your C# application to make sure they are functionally (rather than syntactically) identical to what is in the VB6 application, and don't conflict with built-in functionality in .NET (such as garbage collection.)

    If you are able to post some excerpts from the offending code, we may be able to offer more help.
  • Thanks for the replies so far. I agree with ewe0006 that, as per what Microsoft says, all .NET languages are equal, i.e. one can't do more or be faster than another.

    So I guess that really just leaves the code side of things...

    This is an excerpt from the VB6 code at the point where it versus C# is so much quicker:

    Set PcCmds = PcPart.Commands

    For Each PcCmd In PcCmds
    If PcCmd.IsTraceField Then

    traceName = PcCmd.GetText(TRACE_NAME, 0)

    Select Case traceName

    Case "Die Number" 'All
    dieNumber = PcCmd.GetText(TRACE_VALUE, 0)
    Case "Part Number" 'All
    partNumber = PcCmd.GetText(TRACE_VALUE, 0)
    Case "Cast Date" 'All
    castDate = PcCmd.GetText(TRACE_VALUE, 0)

    Other case statements...

    End Select
    End If
    Next PcCmd

    For Each PcCmd In PcCmds
    If PcCmd.IsDimension Then

    commandId = PcCmd.ID
    axis = PcCmd.AxisLetter
    measured = PcCmd.Measured

    'write to file
    Write #1, dieNumber, partNumber, castDate, axis, measured, etc, etc...
    End If
    Next PcCmd

    This is how we translated the above into C# code:

    PCCommands = PCPart.Commands;

    for (int i = 1; i <= PCPart.Commands.Count; i++)
    {
    var PCCommand = PCPart.Commands.Item(i);

    if (PCCommand != null)
    {
    if (PCCommand.IsTraceField)
    {
    var traceName = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_NAME, 0);

    switch (traceName)
    {
    case "Die Number":
    dieNumber = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
    break;
    case "Part Number":
    partNumber = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
    break;
    case "Cast Date":
    this.CastDate = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
    break;

    other case statements

    }
    }
    }
    }

    for (int i = 1; i <= PCPart.Commands.Count; i++)
    {
    var PCCommand = PCPart.Commands.Item(i);

    var executedCommands = PCPart.ExecutedCommands;
    var executedCommandsCount = executedCommands.Count;

    if (PCCommand != null)
    {
    if (PCCommand.IsDimension)
    {
    pcCommandId = PCCommand.ID;
    axis = PCCommand.AxisLetter
    measured = PCCommand.Measured

    //write dieNumber, partNumber, pcCommandId, axis, measured etc, etc to file
    }
    }
    }

    If there's anything in the above folks can see where we're functionally different in C#, please shout. NOTE: the VB6 code above may be a horribly inefficient way to bring the information back from PC-DMIS so if there's a better way, I'd be glad to hear about it as all I'm confident to do is port the existing code over rather than rewriting it from scratch. All we're basically trying to do is automate the creation of a text file with dimensional and some other information. If there's an article of some other help documentation where there's an example, I'd appreciate if you would post a link as I can't find anything myself.

    Thanks!
  • From what you posted, nothing really jumps out at me but there are a few things to look at closer. I notice you have a comment line just to show that there is file access activity, but what's going on when you write to the text file? A few things I could imagine happening here:


    • Opening a new memory stream, writing a line, then closing the stream on each iteration of the loop;
    • Writing to a memory stream that sits open for the duration of the loop;
    • Adding the line to a string or collection on each iteration, then writing all lines to a file after the loop has completed.



    The first method here would be the least efficient of the bunch, and depending on how the file access is performed could add up to real time. Performance wise, the second and third methods would be about the same, but the third method is the way I prefer to do file writes so that the stream is open for the shortest amount of time, all of the data gets written in one go, and it minimizes the possibility for exceptions and whatnot to occur while the stream is open since there isn't other work going on at the same time.

    Given the code you posted, what are the "executedCommands" and "executedCommandsCount" being used for? Those variables don't appear to exist in the original application, and I don't see where the values you are assigning to them are actually being used, but you are declaring and assigning a value to them on every iteration. Lastly, I would try to move as much of your declarations as you can to outside of the loop:

    	PCCommands = PCPart.Commands;
    
    	var traceName;
    	var PCCommand;
    	[COLOR="#FF0000"]var executedCommands;[/COLOR] [COLOR="#008000"]//What is this for?[/COLOR]
    	[COLOR="#FF0000"]var executedCommandsCount;[/COLOR] [COLOR="#008000"]//What is this for?[/COLOR]
    
    	for (int i = 1; i <= PCPart.Commands.Count; i++)
    	{
    		PCCommand = PCPart.Commands.Item(i);
    
    		if (PCCommand != null)
    		{
    			if (PCCommand.IsTraceField)
    			{
    				traceName = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_NA ME, 0);
    
    				switch (traceName)
    				{
    					case "Die Number":
    					dieNumber = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VA LUE, 0);
    					break;
    					case "Part Number": 
    					partNumber = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VA LUE, 0);
    					break;
    					case "Cast Date": 
    					this.CastDate = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VA LUE, 0);
    					break;
    
    					other case statements
    
    				}
    			}
    		}
    	}
    
    	for (int i = 1; i <= PCPart.Commands.Count; i++)
    	{
    		PCCommand = PCPart.Commands.Item(i);
    
    		executedCommands = PCPart.ExecutedCommands;
    		executedCommandsCount = executedCommands.Count;
    
    		if (PCCommand != null)
    		{
    			if (PCCommand.IsDimension)
    			{
    				pcCommandId = PCCommand.ID;
    				axis = PCCommand.AxisLetter
    				measured = PCCommand.Measured
    
    				//write dieNumber, partNumber, pcCommandId, axis, measured etc, etc to file [COLOR="#008000"]\\What is happening here?[/COLOR]
    			}
    		}
    	}
    
  • The big difference lies in

    For Each PcCmd In PcCmds


    vs.

    for (int i = 1; i <= PCPart.Commands.Count; i++) 
    {
       var PCCommand = PCPart.Commands.Item(i);
    

    Indexing into .Commands seems to be quite a bit slower compared to getting the next one through For Each.... If the modern language doesn't support For Each, I think you can use a while-loop and PCCommand.Next (or something similar) instead.

    The "write to file" part is probably also much slower in .NET than in VB6.
  • Hi again folks,

    Thanks so much for the responses.

    Sorry I abbreviated the file writing piece. Basically all we do inside the loop is add the data to a stringbuilder object, then after the loop finishes we write it to file in one action. The executedCommands and executedCommandsCount were just there as I was trying to see if there were less executed commands than PCPart.Commands.Count. The plan was to use PCPart.ExecutedCommands in the loop if it had fewer than PCPart.Commands. However it doesn't so I'll remove those from the code.

    The apparent slowness of indexing into commands (i.e. var PCCommand = PCPart.Commands.Item(i)) sounds like it's worth exploring so I'll have a look at how we code that differently.

    Many thanks again for the suggestions. I'll be sure to circle back with any updates from my side in due course (it may be a day or 2 as the Scirocco machines are down for an upgrade right now).
  • Just a thought.

    Maybe the problem is in the 'for (...,PCPart.Commands.Count,...)' line. Does this query the number of commands in PC-DMIS for every iteration of the loop? If yes, this would slow it down (use a variable instead).
  • Just a thought.

    Maybe the problem is in the 'for (...,PCPart.Commands.Count,...)' line. Does this query the number of commands in PC-DMIS for every iteration of the loop? If yes, this would slow it down (use a variable instead).


    Sounds good, I'll try that. Thanks
  • The big difference lies in

    For Each PcCmd In PcCmds


    vs.

    for (int i = 1; i <= PCPart.Commands.Count; i++) 
    {
       var PCCommand = PCPart.Commands.Item(i);
    

    Indexing into .Commands seems to be quite a bit slower compared to getting the next one through For Each.... If the modern language doesn't support For Each, I think you can use a while-loop and PCCommand.Next (or something similar) instead.

    The "write to file" part is probably also much slower in .NET than in VB6.


    If memory serves me correctly, C#'s "foreach" extension only works on collections that implement IEnumerable, so I don't think it will work on PCDLRN.Commands without first casting into something else.