See and hear the effects of Garbage Collection

Sometimes you forget that GC’s occur: it’s hard to see it’s effect, but what does Garbage Collection do to your code?

 

A long time ago (3 decades!) I used Fortran and Assembly code for a PDP-11 16 bit computer to design real time signal processing systems. Two of these were used to detect submarines: one would make a very loud noise (think a very big sonar Ping), and the other would pretend to be a submarine by listening to the sound and echoing back, at various set strengths (1X, 10X, 100X), as if it were a particular kind of submarine pointing in various directions. “I’m a Submarine”, “I’m a Submarine” “I’M A SUBMARINE”. If all 3 of these echoes were heard, then we know we heard all signal strengths. If only 2,then we know by how much the target was missed.

 

The sound maker generated a huge amount of noise underwater via controlling precisely the release of an array of high pressure air guns. The sound wavefront could be steered via subtle variations in firing timing. Sound travels so well and fast underwater!

A typical scuba tank is about 8 inches in diameter and 20 inches long, holding maybe 3000 PSI and is very dangerous (think exploding shark a la Jaws). Our array of 10 air guns were 10 inches in diameter and 20 feet long, with 10,000 PSI.

 

The listener computer on a different boat hundreds of miles away used an underwater microphone to record the sounds into memory at a sub 1Khz sampling rate using an Analog to Digital converter. The code received the data via DMA (Direct Memory Access) and double-buffered it: as one buffer was being filled, the other would be processed, such as copied to an Array Processor to do a Fourier Transform.

The signal would then be convolved (multiplications, additions) with various prerecorded echoes from a submarine, and then the signal was D-A converted to a sound blasted via an underwater speaker.

 

The CPU needed to be fast enough to keep up with the incoming raw data: if I tuned it to sample a little bit faster, bad things would occur: missing data, distortion, etc.

I had to have precise control of these real time systems for accuracy and reliability.

 

Designing a real-time system with a Garbage Collected system could be quite difficult in tight performance scenarios. A GC during a sampling could cause lost data.

 

What does a GC actually do? Every thread that runs managed code has to be frozen during the entire GC, which means your application pauses. All objects are scanned to see if they are referenced by any other objects. Lonely objects are considered freeable: objects are moved around in memory to coalesce free space, and object references are updated with the new locations.

 

So how can you see or hear the effect of GC?

 

Start Visual Studio

File->New->Project->C# WPF Application. Name it SeeHearGC

 

Replace the MainWindow.Xaml.CS with the contents below.

If you run under the debugger (F5), then Uncheck this option: Project->Properties->Debug->Enable the Visual Studio Hosting Process

 

Click with right button for just sounds, or left mouse for color display.

 

The code alternately raster scans the form, painting pink squares. Then it repeats with blue. When a GC occurs, the color for that square is inverted. A Beep can be heard at the end of each horizontal scan. If a GC occurs, the square color is inverted, so you can see when it occurred.

 

You can also run PerfMon (start->Run->PerfMon) to see the GC behavior of any managed application, even when the program is running,

Select Performance Monitor in the Treeview, delete any counters in the display (Hit the red x), then hit the green “+” to add a couple counters.

Choose <Local computer> , scroll to and expand .NET CLR Memory, and choose “#Gen 0 Collections” and “% Time in GC”. In the Instance listbox, choose the process: SeeHearGC.

 

Originally I wanted to control the speaker directly because human hearing can detect subtle differences in timing. However, I had to settle on Beeps.

 

Perhaps in a later post, I’ll write out to a WAV file or otherwise record the GC data for further analysis.

 

See also:

How to filter out unwanted sounds via Fourier Transform

Heartbeat: Garbage collection in VFP and .NET are similar

 

 

<Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.ComponentModel;

using System.Windows.Threading;

using System.Runtime.InteropServices;

using System.Diagnostics;

using System.Reflection;

namespace WpfApplication1

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        public const int xCnt = 5;

        public const int yCnt = 10;

        public const int xSize = 19;

        public const int ySize = 19;

        TextBlock[,] _btns = new TextBlock[xCnt, yCnt];

        [DllImport("kernel32.dll")]

        public static extern bool Beep(int BeepFreq, int BeepDuration);

        PerformanceCounter _perfCounterGC;

        int _nGCs = 0;

        public MainWindow()

        {

            InitializeComponent();

            // Uncheck this option: Project->Properties->Debug->Enable the Visual Studio Hosting Process

            var thisAsm = System.IO.Path.GetFileNameWithoutExtension( Assembly.GetEntryAssembly().Location);

       _perfCounterGC = new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", thisAsm);

           

            SolidColorBrush[] brushes = new SolidColorBrush[2];

            brushes[0] = new SolidColorBrush(Colors.LightSalmon);

            brushes[1] = new SolidColorBrush(Colors.LightBlue);

            this.Title = "Click on the Red";

            int i, x, y;

            this.Loaded += (o, e) =>

            {

                var surface = new Canvas();

                this.Content = surface;

                for (y = 0; y < yCnt; y++)

                {

                    for (x = 0; x < xCnt; x++)

                    {

                        var rect = new TextBlock()

                        {

                            Background = brushes[0],

                            HorizontalAlignment = System.Windows.HorizontalAlignment.Left,

                            VerticalAlignment = System.Windows.VerticalAlignment.Top,

                          Width = xSize,

                            Height = ySize

                        };

                        _btns[x, y] = rect;

                        surface.Children.Add(rect);

                        Canvas.SetLeft(rect, x * xSize);

                Canvas.SetTop(rect, y * ySize);

                    }

                }

                int currentMainColor = 0;

                this.MouseDown += (om, em) =>

                {

                    bool fDoVisual = true;

                    if (Mouse.RightButton == MouseButtonState.Pressed)

                    {

                        fDoVisual = false;

                    }

                    var txt1 = "1";

                    var txtToUse = string.Empty;

                    int colorToUse = 0;

                    int curGCs = 0;

                    // main loop: we want to minimize memory allocations

                    for (i = 0; i < 5; i++)

                    {

                        if (fDoVisual)

                        {

                            currentMainColor = 1 - currentMainColor;

                        }

                        for (y = 0; y < yCnt; y++)

                        {

                            Beep(440, 160);

                            if (fDoVisual)

                            {

                                for (x = 0; x < xCnt; x++)

                                {

                                    curGCs = (int)_perfCounterGC.NextValue();

                                    colorToUse = currentMainColor;

                                    if (curGCs != _nGCs)

                                    {

                                        txtToUse = txt1; // (curGCs - _nGCs).ToString();

                                        _nGCs = curGCs;

                                        colorToUse = 1 - colorToUse;

                                    }

                                    else

                                    {

                                        txtToUse = string.Empty;

     }

                                    _btns[x, y].Text = txtToUse;

                                    _btns[x, y].Background = brushes[colorToUse];

                                    { // we want to wait til after render by synchronously running low pri empty code

                                        this.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>

                                        {

                                        }

                                        ));

                                    }

                                }

                            }

                            else

                            {

                                System.Threading.Thread.Sleep(100);

                            }

                        }

                    }

                };

            };

        }

    }

}

 

</Code>