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 haven't tested this yet, but this may work for as it returns an IEnumerable object:

    foreach (var PCCommand in PCPart.Commands.NewEnum)
                {
                    if (PCCommand != null)
                    {
                        if (PCCommand.IsTraceField)
                        {
    
  • I haven't tested this yet, but this may work for as it returns an IEnumerable object:

    foreach (var PCCommand in PCPart.Commands.NewEnum)
                {
                    if (PCCommand != null)
                    {
                        if (PCCommand.IsTraceField)
                        {
    


    ...Having said that, I can't find anything in the PC-DMIS help documentation covering the 'Commands.NewEnum' piece, so I'm not too hopeful. I think from memory that I couldn't get that working when I first tried so went with the other approach to use the for loop: 'for (int i = 1; i <= PCPart.Commands.Count; i++)'
  • Looking in the type library, I see .NewEnum declared as IUnknown, which is not exactly an IEnumerable (but might be). Maybe it works if you cast it?
  • I read up on MSDN's definition of the for loop (here: http://msdn.microsoft.com/en-us/library/ch45axte.aspx), and supposedly the loop initialization is only evaluated once. If that were true, there would be no performance difference between the following loops:

                    //Test loop 1
                    for (int i = 1; i < pcd.Commands.Count; i++)
                    {
                    }
    
    
                    //Test loop 2
                    int cmdCnt = pcd.Commands.Count;
                    for (int i = 1; i < cmdCnt; i++)
                    {
                    }


    I set up a little test harness to try both formats two times (so we can see if there are any first-run performance hits) on a program with 141 commands, and got the following results:

    1. Test loop 1: 1822ms
    2. Test loop 2: 13ms
    3. Test loop 1: 1840ms
    4. Test loop 2: 17ms



    A bit counter-intuitive to how Microsoft says the loop should work, but definitely a clear performance difference... Good call, Rondog!

    I built on the latter of the two loops (given its superiority) to start evaluating command properties:

    //Test loop 3
                    cmdCnt = pcd.Commands.Count;
                    PCDLRN.Command cmd;
                    for (int i = 1; i < cmdCnt; i++)
                    {
                        cmd = pcd.Commands.Item(i);
                        if (cmd != null)
                        {
                            if (cmd.IsTraceField)
                            {
                            }
                        }
                    }
    
    
                    //Test loop 4
                    cmdCnt = pcd.Commands.Count;
                    for (int i = 1; i < cmdCnt; i++)
                    {
                        if (pcd.Commands.Item(i) != null)
                        {
                            if (pcd.Commands.Item(i).IsTraceField)
                            {
    
                            }
                        }
                    }


    Running each loop twice, I got:

    1. Test loop 3: 2677ms
    2. Test loop 4: 5181ms
    3. Test loop 3: 2645ms
    4. Test loop 4: 5407ms



    It seems any time you directly address a property or method in a PC-DMIS COM object, your performance is going to take a massive hit. If that is the case, you'll probably want to combine all of your data extraction into one loop:

    cmdCnt = pcd.Commands.Count;
                    PCDLRN.Command cmd;
                    for (int i = 1; i < cmdCnt; i++)
                    {
                        cmd = pcd.Commands.Item(i);
                        if (cmd != null)
                        {
                            if (cmd.IsTraceField)
                            {
                                //handle trace field...
                            }
                            if (cmd.IsDimension)
                            {
                                //handle dimension...
                            }
                            //etc...
                        }
                    }
  • ... Good call, Rondog!


    Party time... Excellent...
  • Thanks again for all responses. I'll post an update soon on how it all works out!
  • I read up on MSDN's definition of the for loop (here: http://msdn.microsoft.com/en-us/library/ch45axte.aspx), and supposedly the loop initialization is only evaluated once. If that were true, there would be no performance difference between the following loops:

                    //Test loop 1
                    for (int i = 1; i < pcd.Commands.Count; i++)
                    {
                    }
    
    
                    //Test loop 2
                    int cmdCnt = pcd.Commands.Count;
                    for (int i = 1; i < cmdCnt; i++)
                    {
                    }




    Yes, there would - the initialization part is only int i = 1;, the test part must be done on each turn through the loop.
  • Thanks again for all responses. I'll post an update soon on how it all works out!


    Thanks to y'all the time to produce the text file has gone from approx. 2.5 minutes to a little over 1 minute. This is the final code in case it helps anyone else or if anyone has further suggestions to make it even faster:

    
            private void OutputPcDmisDataToTextFile()
            {
                StringBuilder sb = new StringBuilder();
    
                //these strings will hold the pc-dmis trace data
                string dieNumber = string.Empty;
                string partNumber = string.Empty;            
                string productType = string.Empty;
                string castingMachine = string.Empty;
                string cmmOperator = string.Empty;
                //other strings go here
    
                var PCCommands = PCPart.Commands;
    
                string pcCommandId = string.Empty;
    
                var pcDmisOutputList = new List<PcDmisOutput>(); //PcDmisOutput is just a small class that holds command ID and measurement/deviation data to allow us to go from 2 loops through the pc-dmis data down to 1
    
                var commandsCount = PCPart.Commands.Count;
    
                for (int i = 1; i <= commandsCount; i++)
                {
                    PCCommand = PCPart.Commands.Item(i);
    
                    if (PCCommand != null)
                    {
                        if (PCCommand.IsTraceField)
                        {
                            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":  //All
                                    partNumber = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
                                    break;
                                case "Cast Date":    //All
                                    this.CastDate = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
                                    break;
                                case "Product Type": //All
                                    productType = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
                                    break;
                                case "Casting Machine":  //puma cast
                                    castingMachine = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
                                    break;
                                case "CMM Operator": //All
                                    cmmOperator = PCCommand.GetText(PCDLRN.ENUM_FIELD_TYPES.TRACE_VALUE, 0);
                                    break;
                                //other trace info is brought back here
                            }                        
                        }
    
                        if (PCCommand.IsDimension)
                        {
                            //sometimes we have a command id but no axis so these statements ensure we have both
                            if (!string.IsNullOrEmpty(PCCommand.ID) && !string.IsNullOrEmpty(PCCommand.DimensionCommand.AxisLetter))
                            {
                                pcDmisOutputList.Add(new PcDmisOutput(PCCommand.ID, PCCommand.DimensionCommand.Deviation, PCCommand.DimensionCommand.Measured));
    
                            }
                            else if (!string.IsNullOrEmpty(PCCommand.ID) && string.IsNullOrEmpty(PCCommand.DimensionCommand.AxisLetter))
                            {
                                pcCommandId = PCCommand.ID;                            
                            }
                            else if (string.IsNullOrEmpty(PCCommand.ID) && !string.IsNullOrEmpty(PCCommand.DimensionCommand.AxisLetter))
                            {                            
                                pcDmisOutputList.Add(new PcDmisOutput(pcCommandId, PCCommand.DimensionCommand.Deviation, PCCommand.DimensionCommand.Measured));
                            }                        
                        }
                    }
                }
    
                foreach (var pcDmisOutput in pcDmisOutputList)
                {
                    sb.AppendLine(string.Format(//omitting my code but this is where we build up a string with the following: dieNumber partNumber productType castingMachine cmmOperator, pcDmisOutput.CommandId, pcDmisOutput.GetDeviationOrMeasurement()
                    //note: the GetDeviationOrMeasurement() method returns either the deviation or measurement based on the one that pc-dmis populated when it performed the measurement
                }
    
                writeToDataFile(sb.ToString());
            }
    
    
  • 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!


    This type of stuff is cool, but waaayyy over my head. I love when people post code here though...it shows me what is possible and I can nip it and change it to help me do something similar...this one is a bit tougher than I am used to...
  • Thanks to y'all the time to produce the text file has gone from approx. 2.5 minutes to a little over 1 minute. This is the final code in case it helps anyone else or if anyone has further suggestions to make it even faster...


    I've got another idea... You currently have your loop evaluating every command to know if it is a trace field, and then if it is a dimension even if you already know it's a trace field. The simplest edit I can think of would be throwing a 'continue' inside the first 'if' block:

    for (int i = 1; i < cmdCnt; i++)
                    {
                        cmd = pcd.Commands.Item(i);
                        if (cmd != null)
                        {
                            if (cmd.IsTraceField)
                            {
                                //handle trace field...
                                [COLOR="#FF0000"]continue;[/COLOR]
                            }
                            if (cmd.IsDimension)
                            {
                                //handle dimension...
                            }
                            //etc...
                        }
                    }


    That way, once you already know the command is a trace field, you don't bother checking to see if it is a dimension.

    Alternatively, you could use an 'else if', though there are weird cases where all parts of 'if' statements are evaluated and it *might* not help anything...

    for (int i = 1; i < cmdCnt; i++)
                    {
                        cmd = pcd.Commands.Item(i);
                        if (cmd != null)
                        {
                            if (cmd.IsTraceField)
                            {
                                //handle trace field...
                            }
                            else if (cmd.IsDimension)
                            {
                                //handle dimension...
                            }
                            //etc...
                        }
                    }


    Lastly, I would move whichever command type your CMM programs have more of to be evaluated first. So, if you have more dimensions than trace fields, evaluate the dimension first. If you do this in combination with either the 'continue' or 'if...else if', you'll have more times where the loop can iterate early and reduce your calls on the PC-DMIS command objects:

    for (int i = 1; i < cmdCnt; i++)
                    {
                        cmd = pcd.Commands.Item(i);
                        if (cmd != null)
                        {
                            if (cmd.IsDimension)
                            {
                                //handle dimension...
                                continue;
                            }
                            if (cmd.IsTraceField)
                            {
                                //handle trace field...
                            }
                            //etc...
                        }
                    }


    That should shave off a little bit more of your execution time.