Optimisation Advice Needed

Hey Guys,

I’m now finding that, however wonderful programming in C# is, my current application has gone past the limitations of what it can do, at least on an ARM7 chip. I have now gone onto translating key parts of my code into C, and it has improved the efficiency quite dramatically in many areas.

I wanted your opinion on whether the following bits of code would grant a reasonable performance improvement if done in RLP - These are methods that are run very often, but ones that seem to me pretty native to begin with, hence making me doubtful of the value of said translation vs the benefits of netmf.

Code 1: from Spherical to Polar co-ordinates conversion:

static public int[] ToBearing(float[] pos, float wptLat, float wptLng)
        {
            int _radius = 6371000;
            double lat1 = Radians(pos[1]), dLong = Radians(wptLat - pos[0]),
                lat2 = Radians(wptLng), bearing, distance;

            //Distance
            distance = MathEx.Acos(MathEx.Sin(lat1) * MathEx.Sin(lat2) +
                MathEx.Cos(lat1) * MathEx.Cos(lat2) * MathEx.Cos(dLong)) * _radius;

            //Bearing
            bearing = MathEx.Atan2(MathEx.Sin(dLong) * MathEx.Cos(lat2),
                MathEx.Cos(lat1) * MathEx.Sin(lat2) - MathEx.Sin(lat1) * MathEx.Cos(lat2) * MathEx.Cos(dLong));

            return new int[2] { (int)MathEx.Round(distance), (int)MathEx.Round((Degrees(bearing) + 360) % 360) };
        }

Code 2: GPS Interfacing:

 public class GpsApi
    {
        private Thread ReadGps;
        private SerialPort gpsUart;

        public float[] pos;
        public float speed;
        public int fix;
        public int magVar;

        public GpsApi()
        {
            gpsUart = new SerialPort("COM3", 57600);
            fix = 0;
            pos = new float[2];

            ReadGps = new Thread(UpdateData);
        }

        public float[] GetPosData()
        {
            return pos;
        }

        public void UpdateData()
        {
            gpsUart.Open();

            while (true)
            {
                gpsUart.DiscardInBuffer();

                string[] data;
                byte[] rxBytes = new byte[256];
                char[] chars;
                string[] nmeaStr;

                while (gpsUart.BytesToRead != 256)
                    Thread.Sleep(20);

                gpsUart.Read(rxBytes, 0, 256);

                try
                {
                    chars = UTF8Encoding.UTF8.GetChars(rxBytes);
                    string rxData = new string(chars);

                    nmeaStr = rxData.Split('$', '\r','\n');
                    
                }
                catch (Exception ex)
                {
                    continue;
                }

                for (int i = nmeaStr.Length - 4; i >= 3; i--)
                {
                    if (nmeaStr[i] != "")
                    {
                        data = nmeaStr[i].Split(',');

                        switch (data[0])
                        {
                            case "GPGLL":
                                if (GetFixStatus(data[6]))
                                {
                                    pos[0] = GetDecimalDeg(data[1], 2) * GetSign(data[2]);//lat
                                    pos[1] = GetDecimalDeg(data[3], 3) * GetSign(data[4]);//long
                                    fix = 1;
                                }
                                else
                                    fix = 0;
                                break;
                            case "GPVTG":
                                speed = (float)double.Parse(data[7]);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }

        private float GetDecimalDeg(string degrees, int degLength)
        {
            return (float)(double.Parse(degrees.Substring(0, degLength)) + (double.Parse(degrees.Substring(2)) / 60));
        }

        private int GetSign(string sign)
        {
            if (sign == "W" || sign == "S")
                return -1;
            else
                return 1;
        }

        private bool GetFixStatus(string fixStatus)
        {
            if (fixStatus == "A")
                 return true;
            else
                return false;
        }


    }

PS If anyone has any advice on more efficient GPS reading methods, I’d really appreciate it. I never really quite understood what was considered the optimal way of reading serial ports.

Either way, any point in implementing those in RLP?

I think someone with the knowledge should answer. I’d be very interested to know too.

My instinct tells me that the calls into the maths classes should already be native. One way to find out is to check the NETMF source code. If it is found in C++ there RLP is going to help very little IMHO.

Yes you will make the system slower if you reimplent any method. Everything is already native and optimized.

What will make things faster is moving your processing loops. You know, the code that loops 100+ times doing something.

If we ate talking gps then parse everything in RLP.

Thanks Gus, I’ll move the GPS over to RLP. What about the first function? The one involving a lot of trig?

Seems to me like your serial port handling is not going to be reliable. Here’s the issue I see with it.


               gpsUart.DiscardInBuffer();

              while (gpsUart.BytesToRead != 256)
                    Thread.Sleep(20);
 
                gpsUart.Read(rxBytes, 0, 256);

So in recap, you discard anything in the buffer, and then wait for EXACTLY 256 bytes to be in the input buffer, with a 20msec gap between checks, and then you read those 256 bytes.

What happens if the GPS has 200 bytes in the buffer and an additional sentence comes in from the GPS and pushes it to 260 bytes? You could stay in that loop for ever. Also, what happens if you enter this code block when part of the sentence you want has already been sent, so you discard it; you may never recover.

There are some GPS parsing examples on the codeshare, like http://code.tinyclr.com/project/89/geoinformationsystemsmicrogps/ as a starting reference. Worth digging into that.

I would do something like this: (I don’t know if it compiles or works, I typed it directly to the forum message input)


byte[] buffer = new byte[1024];
int bufferOffset = 0;
Decoder decoder = Encoding.UTF8.GetDecoder();

while (!terminated)
{
   while (serial.BytesToRead)
   {
      int bytesRead = serial.Read(buffer, bufferOffset, buffer.Length - bufferOffset);

      bufferOffset += bytesRead;

      while (bufferOffset > 0)
      {
         int index = Array.IndexOf(buffer, (byte)'\n', 0, bufferOffset);

         if (index < 0)
            break; // No newline char found in buffer

         // Convert message to string
         int bytesUsed, charsUsed;
         bool completed;
         char[] chars = new char[index];
         decoder.Convert(buffer, 0, index, chars, 0, index, true, out bytesUsed, out charsUsed, out completed);
         Process(new string(chars));

         // Remove message from buffer
         bufferOffset -= (index + 1);
         if (bufferOffset > 0)
            Array.Copy(buffer, index + 1, buffer, 0, bufferOffset);
      }      
   }
   Thread.Sleep(5);
}

@ Wouter - Don’t know if it runs or not either but I see some brilliant techniques in there that I think will help with some serial port handling I’ve been fighting with this weekend. Going to bed now but I’m going to integrate some of it in tomorrow. I think I’ve spent half of my NETMF time in the past several months fighting with this type of code. There has to be a better way to get data between components. This feels like drinking from a fire hydrant.

static public int[] ToBearing(float[] pos, float wptLat, float wptLng)
        {
            int _radius = 6371000;
            double lat1 = Radians(pos[1]), dLong = Radians(wptLat - pos[0]),
                lat2 = Radians(wptLng), bearing, distance;
 
            //Distance
            distance = MathEx.Acos(MathEx.Sin(lat1) * MathEx.Sin(lat2) +
                MathEx.Cos(lat1) * MathEx.Cos(lat2) * MathEx.Cos(dLong)) * _radius;
 
            //Bearing
            bearing = MathEx.Atan2(MathEx.Sin(dLong) * MathEx.Cos(lat2),
                MathEx.Cos(lat1) * MathEx.Sin(lat2) - MathEx.Sin(lat1) * MathEx.Cos(lat2) * MathEx.Cos(dLong));
 
            return new int[2] { (int)MathEx.Round(distance), (int)MathEx.Round((Degrees(bearing) + 360) % 360) };
        }

If this method is called very often, you can also make it faster by using more variables (if there is enough memory). For example you compute Cos(lat1) and Cos(lat2) at least 2 times each. You could do something like:

double cosLat1 = Math.Cos(lat1), cosLat2 = Math.Cos(lat2);

and then just use cosLat1 and cosLat2 where ever you need it. You don’t need to recompute it every time since it takes some clock cycles to do it.

Thanks Brett, I’ll look at those examples.

And the buffer on a USBizi is 256 bytes, so it can’t get any higher - I’m just checking if I have a full buffer.

Wouter - I really like that approach, I may have to nick ideas from it.

Since we’re already talking about optimization, any clue why SerialPort.Flush() takes around 1.4 seconds? (14 million processor ticks)… Seems a bit excessive. I have resorted to writing 256 bytes of 0x00 down the buffer, its much faster. Frankly I still haven’t worked out why the data on my radio link isn’t coming through if I don’t flush the port after writing it.

you’re wasting time waiting for a full buffer, plus you then risk losing characters.

You have to double buffer here. Have one process reading bytes from the serial port and stuffing it in a circular buffer. Another consumes this buffer and un-buffers the characters it needs.

Take a look at Wouter’s process or the other GPS focussed examples that might help.

I did - edited Wouters code a bit and it works perfectly, thanks :slight_smile:

I would however appriciate some insight into the workings of SerialPort.Write() and SerialPort.Flush().

Currently

            lock (radioLock)
            {
                DrivePin(transmitEnable);
                
                radio.Write(preq, 0, 24);
                radio.Write(packet, 0, packet.Length);
                FloatPin(transmitEnable);
                radio.Write(blank, 0, 160);
            }

Works just fine, with data coming across the other side and no losses

            lock (radioLock)
            {
                DrivePin(transmitEnable);
                
                radio.Write(preq, 0, 24);
                radio.Write(packet, 0, packet.Length);
                radio.Flush();
                FloatPin(transmitEnable);
            }

Once again, works, but flush takes ages (14million ticks anyone?)

            lock (radioLock)
            {
                DrivePin(transmitEnable);
                
                radio.Write(preq, 0, 24);
                for (int i = 0; i < 4; i++)
                   radio.Write(packet, 0, packet.Length);
                
                FloatPin(transmitEnable);
            }

Works, all four messages come across just fine (tried numbering them etc they come across perfectly)

But…

            lock (radioLock)
            {
                DrivePin(transmitEnable);
                
                radio.Write(preq, 0, 24);
                radio.Write(packet, 0, packet.Length);
                
                FloatPin(transmitEnable);
            }

Does absolutely nothing, or at least so it seems on the receiving end.

So for some reason if there isn’t more data in the write buffer (more than about 150 bytes) it doesn’t get written, at least not properly. Or a SerialPort.Flush does the trick. I haven’t a clue what the method actually does on hardware level, so its difficult for me to work out why it works.

The DrivePin and FloatPin respectively enable and disable the radio transmissions. This suggest the issue isn’t anything to do with the radio modules. Actions after the radio modules have been powered down affect the transmission.

I am rather confused. Any ideas? Using any of the above hacky ways really slows down my code.

Personally, the workings of this don’t seem too relevant to your problem, do they?

Flush() just forces the sending of the data and flushes the buffer, which makes sense to be a blocking call since it’s assumed that you only call flush if you need to programmatically know when the data you’ve transmitted has gone. That will be totally dependent on the baud rate your port operates at and how many bytes you have to send. To prove that, just send one byte and then do a flush and see how long it takes? Or set up the port with a greater baud rate and then do the same and see how long it takes then? SerialPort Members | Microsoft Learn is the reference to the 4.1 serialport object (which I assume is what the radio object in your code above is).

edit: I’ve just re-checked all the information you’ve shown to date, and it appeared you were only talking about reading from a serial GPS before. Why are we now writing ? And what to - by any chance is it a USB to Serial adapter? Pretty sure there was a post about how a USB<->serial device had a buffer that didn’t send data effectively, I’ll see if I can find it. But a simple serial port test is to set up connection to teraterm or your favourite terminal program and you write data to it - that should show you if there’s any glitching in the data. I’ll also do this test on my own device when I get close to one.

Sorry, I must have not specified previously - I’m using a radio link. Completely transparent. Hence packet integrity is critical, something that seems to be neglected unless there is a lot of data to write in the buffer. At least I think thats the problem.

Writing to serial or to a cdc works just fine.

Went a bit off topic - Thought that since you guys are already on this thread i might as well milk it and get some help on this, it’s been bothering me for ages :).

I have little knowledge of the inner workings of SerialPort, hence I thought this might be a querk with the class itself. Something along the lines of “If there isn’t a huge amount of data in the buffer, you can take your time outputting it”.