Main Site Documentation

Reduce GC calls


#1

Guys,

I have the following basic code that does nothing more then counting pulses with interrupts on 3 IO’s. I do see now and then that the GC kicks in. Can this code be optimized to reduce the GC calls?


namespace FEZ_Panda_Counters
{
  public class Program
  {
    static FEZ_Pin.Interrupt gasPin = FEZ_Pin.Interrupt.Di7;
    static FEZ_Pin.Interrupt watPin = FEZ_Pin.Interrupt.Di6;
    static FEZ_Pin.Interrupt elePin = FEZ_Pin.Interrupt.Di5;

    static int g_Counter;
    static long g_prevPulse;
    static long g_lastPulse;
    static float g_duration;

    static int w_Counter;
    static long w_prevPulse;
    static long w_lastPulse;
    static float w_duration;

    static int e_Counter;
    static long e_prevPulse;
    static long e_lastPulse;
    static float e_duration;

    static float tps = TimeSpan.TicksPerSecond;

    public static void Main()
    {
      InterruptPort g = new InterruptPort((Cpu.Pin)gasPin, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
      InterruptPort w = new InterruptPort((Cpu.Pin)watPin, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
      InterruptPort e = new InterruptPort((Cpu.Pin)elePin, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
      g.OnInterrupt += new NativeEventHandler(g_OnInterrupt);
      w.OnInterrupt += new NativeEventHandler(w_OnInterrupt);
      e.OnInterrupt += new NativeEventHandler(e_OnInterrupt);

      Thread.Sleep(Timeout.Infinite);
    }

    static void g_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      g_Counter++;
      g_prevPulse = g_lastPulse;
      g_lastPulse = time.Ticks;
      displayCounter();
    }

    static void w_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      w_Counter++;
      w_prevPulse = w_lastPulse;
      w_lastPulse = time.Ticks;
      displayCounter();
    }

    static void e_OnInterrupt(uint data1, uint data2, DateTime time)
    {
      e_Counter++;
      e_prevPulse = e_lastPulse;
      e_lastPulse = time.Ticks;
      displayCounter();
    }

    static void displayCounter()
    { 
      g_duration = (g_lastPulse - g_prevPulse) / tps ;
      w_duration = (w_lastPulse - w_prevPulse) / tps;
      e_duration = (e_lastPulse - e_prevPulse) / tps;

      Debug.Print("gC " + g_Counter + " , gDur: " + g_duration.ToString("F4") + " , wC " + w_Counter + " , wDur: " + w_duration.ToString("F4") + " , eC " + e_Counter + " , eDur: " + e_duration.ToString("F4"));

    }
  }
}

In the end, it should send the counters/times over serial to a Cobra, but I’ll deal with that later.

Thanks


#2

The only place I can see for optimization is Debug.Print call in DisplayCounter().

You can try to add a global strintg variable wich you set with your message and then display that variable.

The trick here is framework won’t release that string message each time you call function -> gc woun’t have to collect it.


#3

It’s your Debug.Print()

This will not work, strings are immutable, you cannot alter a string.
But it’s just a debug message. It’s not necessarily needed.


#4

So removing the Debug.Print() statement and instead sending the data over serial will get rid (or reduce) the GC calls?


#5

It’s not a Debug.Print itself causes the problem. It’s string you forming for Debug.Print

If you send the same string over serial, it will be the same GC messages.


#6

It not so much the Print per se, but the creating a new string each time. The old strings need to get GC’d. So removing the strings (and hence debug) should reduce gc to minimal as possible. Or if it does happen, should exit quickly.


#7

“I do see now and then that the GC kicks in”

There is nothing bad about GC happening “now and then”.

GC is only an issue if it impacts your application’s performance and/or you are getting close to full memory utilization.

What is your issue?


#8

That is another good point. Manual GC is not free either (i.e. in something like C/C++). Even if you could do it manually with an UnAlloc in .Net. At some point, you pay the UnAlloc tax. The GC normally just does it better/faster then we can do it manually. Naturally, there will always be some edge cases. No free lunch however.


#9

Remove the floats, TimeSpan.TicksPerSecond returns a long, and TickCount is an integer.

Create a static byte[] packet, first byte is a begin of header value.
next 8 bytes are the TimeSpan.TicksPerSecond (note that this is a constant, so I shouldn’t send it at all). Following bytes contain your durations (as 4 bytes packed integer). Next byte is a simple XOR checksum and last byte is a end of header value.

The number of bytes in the packet is always the same, so this byte array can be defined global.

Write the packet with SerialPort.Write.

The Cobra then performs the Duration / TicksPerSecond calculation after receiving a valid packet.

Voila, no more garbage to collect :slight_smile:


#10

Btw I see you’re trying to measure water, gas and electricity flow, am I correct?

If so, what kind of measurement devices are you using?


#11

Wouter,

For Electricity I bought a DIN Rail modular kwh meter with S0 output (30 euro), for gas and water I use a hall-effect sensor (2 euro). I’ll try to make some pics later today.


#12

Hi there,

[quote]GC is only an issue if it impacts your application’s performance and/or you are getting close to full memory utilization.

What is your issue?

[/quote]
I have almost an identical problem: I also noticed that the application hang after a while when I imposed a lot of interrupts by scratching with a wire to the ground on the interrupt-pins…
Removing the Debug-statement solves part of the problem I think, but how can I resolve or better prevent a suspended proces? Or is CF not robust enough to handle an overload of interrupts?

Freeck


#13

Like everything else, there is limitation of netmf. You can solve the interrupt problem by adding a capacitor on the pin to lower the possible frequency.


#14

But therefor I also used the glitch-filter, and still the system hangs…
It seems to be an hardware-issue on board the chip? Or is it an error in the driver i.e. not capable to handle an overload of interrupts!?


#15

I think this is a typical thing on any system where if you give it too many interrupts, the system will hang.

IS this something that is expected in your application? If so then you should take care of this in hardware not software. A little capacitor will cover it.


#16

But I stated that I already use the debounch-function of CF with a timespan of 200 ms, so you may expect no more than 5 ints/ps! So why should I add a capacitor? If that cap is mandatory then Iám afraid that the logic of the processor’s interrupt-register or the driver is not designed properly.
I have to give additional information: the main application executes normally, but no interrupts are handled anymore. The interruptline or handler seems to be dead…
The declaration part of the code looks like this:

            InterruptPort keyInt1 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO16, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt2 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO14, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt3 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO39, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt4 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO38, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            TimeSpan ts = new TimeSpan(0, 0, 0, 0, 200);
            Microsoft.SPOT.Hardware.Cpu.GlitchFilterTime = ts;
            keyInt1.OnInterrupt += new NativeEventHandler(key1_OnInterrupt);
            keyInt2.OnInterrupt += new NativeEventHandler(key2_OnInterrupt);
            keyInt3.OnInterrupt += new NativeEventHandler(SpeedUp);
            keyInt4.OnInterrupt += new NativeEventHandler(SpeedDown);

The interrupt handler:

        static void SpeedUp(uint data1, uint data2, DateTime time)
        {
            UpCount++;
            Speed += 5;
            if (Speed > SpeedMax) Speed = SpeedMax;
            //            Debug.Print("LEFT");
        }
 

It seems that the debounchfunction does not work; I recieve more spurious interrupts as expected before the interruptproces hangs.


#17

the glitch-filter doesn’t work the way you think it works - I don’t know the details, but it’s software based. And there’s no way that a 200ms glitch filter in software is a good idea. Fix it in hardware.

Plus, there are glitch filter and edge trigger combinations that don’t work. I’d ordinarily suggest you use Search to find that but that may not be effective just at the moment…


#18

The glitch filter is supposed to help with conditions similar to switch debounce. The input port state is monitored over successive interrupts to assert if a pull down or pull up had actually occurred.

The glitch filter can be configured to the spec. of a particular kind of push button or actuator that you are using.

Debounce will interrupt the micro multiple times; the glitch filter averages the interrupts in relation to time to infer an actual key press.

The glitch filter may not stop those interrupts from occurring and overloading the micro.

Imagine you kept hitting a switch several times a second, there will not be time for anything else except trying to process those press events.


#19

I can’t explain the glitch filter, but I can explain the “dead” interrupts…

Interrupts are called synchronously, which means they are basically launched on their own thread and run, then the framework waits for it to return before calling the next interrupt in the invokation list.

What is probably happening in your code is that you haven’t synchronized access to your UpCount or Speed variables. You can help this out by adorning the function with the MethodImpl.Synchronized attribute and you may be running into concurrency problems. This can happen when the GC runs and events stack up in the background, or when events come in such rapid succession that they stack up.

You may be getting an exception in the event handler which is removing the handler from the invokation list on the caller side, causing it not to function anymore.


#20

[quote]the glitch-filter doesn’t work the way you think it works - I don’t know the details, but it’s software based. And there’s no way that a 200ms glitch filter in software is a good idea. Fix it in hardware.

Plus, there are glitch filter and edge trigger combinations that don’t work. I’d ordinarily suggest you use Search to find that but that may not be effective just at the moment…
[/quote]
Brett thanks, You are right, I assumed the processor handled the edge triggered interrupt incl debounch. In the meantime I figured out how the mechanism seems to work. The first interrupt (switch makes contact) always generates an event no matter the defined debounch time ts!!!; then for that defined period ts no interrupts are handled and perhaps interrupts are disabled!? So this could mean that your assumption isn’t right after all i.e. that the CPU will be overloaded with consequetive interrupts. But who can tell how it realy works…

Then I reduced my code af follows: I moved the debounch definitions and port declarations to the main function (first I “hided” them in a function), and now it works as it should!!

namespace FEZ_Panda_Application1
{
    public class Program
    {
        static bool ledState = false;
         static   int intCount=0 ,oldCount = 0;
        public static void Main()
        {
            OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, ledState);
            //setup the interrupt pin
            TimeSpan ts = new TimeSpan(0, 0, 0, 0, 5000);
            Microsoft.SPOT.Hardware.Cpu.GlitchFilterTime = ts; 
            InterruptPort keyInt1 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO16, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt2 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO14, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt3 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO39, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            InterruptPort keyInt4 = new InterruptPort((Cpu.Pin)FEZ_Pin.Interrupt.IO38, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            keyInt1.OnInterrupt += new NativeEventHandler(key1_OnInterrupt);
            keyInt2.OnInterrupt += new NativeEventHandler(key2_OnInterrupt);
            keyInt3.OnInterrupt += new NativeEventHandler(key3_OnInterrupt);
            keyInt4.OnInterrupt += new NativeEventHandler(key4_OnInterrupt);

            while (true)
            {
                Thread.Sleep(500);
                led.Write(ledState);
                if (intCount != oldCount)
                {
                    Debug.Print("C:" + intCount.ToString());
                    oldCount = intCount;
                }
            }
        }
        static void key1_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            ledState = !ledState;
            Debug.Print("UP");
        }
        static void key2_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            ledState = !ledState;
       }
        static void key3_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            ledState = !ledState;
            intCount++;
       }
        static void key4_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            intCount--;
            ledState = !ledState;
         }
    }

}

After the first touch of the switch I get an event; then for 5 seconds no events are generated no matter how often the switch is pushed…
So a quit elegant simple mechanism in software! (excluding the interrupt register flip-flop… :)).