Search code examples
c#wpfmemoryram

Ways to reduce memory consumption WPF application


Good day.

I have created a little program that creates color palettes. It's okay, it works correctly. Program Screenshot

The whole code is here. The release is here

The texture created is used in 3D modeling as a material. Program works like this:

  1. User chooses colour range and tonestep.
  2. Program iterates through colors saving the current colour in the rectangle.
  3. Puts rectangle into stackpanel (for better managing).
  4. Stackpanel goes into list of stackpanels which then goes on canvas.
  5. The canvas is displayed in the separate window. That canvas must be available for saving as image. (turned out to be useless feature)

I have a little nuisance with this part of code which consumes a lot of RAM.

For example if user choses range 0-255, tonestep 17 then there will be 3375 color boxes. (--(255x255x255)/(17x17x17)--) Consumes 70mb.

if user choses range 0-255, tonestep 5 then there will be 132651 color boxes. (--(255x255x255)/(5x5x5)--) Consumes 600mb!

There are three marks that consume memory the most. When I first created the app I assumed that after each cycle variables "rectangle" and "brushColor" would be released, garbage-collected and reused, but I was completely wrong. They have WeakReference.

In the end I tried caching these variables but then program started to work unexpectedly. No color boxes on final window or all boxes are one color. Or this "Specified Visual is already a child of another Visual or the root of a CompositionTarget". I could not resolve these errors without breaking program so I left it as it is.

So, what better approaches one could use to reduce memory consumption in this case? My implementation is good enough for me but I'd like an advice on how can I make it better. Maybe I missed something obvious or, on the contrary, did something very inefficient like storing color in the rectangle etc.

private void shuffle_colors1(List<StackPanel> stackPanels, Double colorBoxCount, int boxesInSingleRow, int stackPanelsCounter)
        {

            for (int redC = tempR; redC < crMax; redC++)
            {
                for (int greenC = tempG; greenC < cgMax; greenC++)
                {
                    for (int blueC = tempB; blueC < cbMax; blueC++)
                    {
                        var rectangle = new Rectangle(); // MARK 1
                        rectangle.Width = cBoxWidth;
                        rectangle.Height = cBoxHeight;
                        rectangle.Margin = border;

                        stackPanelColor = Color.FromRgb((byte)redC, (byte)greenC, (byte)blueC);
                         
                        brushColor = new SolidColorBrush(stackPanelColor); // MARK 2
                        displayedColors++;

                        rectangle.Fill = brushColor;
                        stackPanels[stackPanelsCounter].Children.Add(rectangle);
                        colorBoxCount++;
                           
                        if (colorBoxCount >= boxesInSingleRow)
                        {
                            AddNewStackPanel(stackPanels, stackPanelsCounter); // MARK 3
                            stackPanelsCounter++;
                            colorBoxCount = 0;
                        }
                              
                        blueC += bts - 1;
                    }
                   greenC += gts - 1;

                }
                redC += rts - 1;
            }
       }

Solution

  • It will be difficult to tell the actual problem without actually doing some debugging, but I might provide some tips:

    Use a Memory profiler

    From your description it sounds like you already does this, but if you make sure to clear out any thing that contain a reference to rectangles or any other UI object they should be eligible for collection. In your example code I cannot see anything that ensures stack panels are re-created or cleared.

    Not removing event handlers is another common reason for leaks.

    But there can also be bugs. I have had some problems with windows accessibility functions keeping references to UI elements after the application itself has released everything. Windows seem to release references after some time, but I still consider it a bug if memory is not freed after a collection. The symptom for this is when UI objects that should be collected is only has a pinnable handle or gcroot as a root reference, with no user code involved. But I fail to find a reference to the problem.

    Reuse objects

    Wpf objects are typically not super cheap to create, so you might want to reuse objects instead of recreating a large number of them. As long as you are not changing the number of boxes I see no reason why you could not simply update the color property of your brushes.

    Limit the number of wpf elements used

    While using separate UI elements might be reasonable for a smaller grid, 130k elements sound to much. So one alternative is just to limit the "tone step" variable to some reasonably large value.

    But if you want to support very large number of colors you will probably benefit from just using a WriteableBitmap to generate your desired color layout. That way your memory usage is directly proportional to the number of pixels you need, with very little overhead.

    Some features, like doing a mouse-over effect, would be more complicated, but most things should still be possible by using regular wpf elements on top of the bitmap to show selection or whatever you want.