2

Yesterday I decided to try my hand at a chess playing program since in my many years I realized I had never written a chess program. I created an AI that evaluates many possible moves/positions and consumes lots of memory. But I figured it would be OK if I cut off my reference to that data each turn. I should get the memory back when needed. But for some reason the objects are sticking in memory. Even when I call GC.Collect() I find that memory usage does not go down, and, thanks to the new GetGeneration method of GC I can see that my objects are stuck in generation 2 (I'm using weak references to hold on to them for the purpose of passing to GetGeneration, of course). The code is not as large as one might think a chess program with an AI would be. It's at https://github.com/bluemonkmn/Chess. Locally I have changed the Program.cs file to make a weak reference to the local board variable. Then I have some code to try to eliminate my reference and check the garbage collection:

board = board.Clone();
validMoves.Clear();
Console.WriteLine(GC.GetGeneration(wr));
GC.Collect(2);
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Console.WriteLine(GC.GetGeneration(wr));

The Clone function (which you can see in the original project if you like - linked above) should be making a copy without any references to old data. And I can't find any other references to old objects. Yet both calls to GC.GetGeneration(wr) output 2. This program's data structure doesn't seem that terribly complicated. I don't have that many variables in the main function so it's not hard to track them down and see what references could be held. Where could the reference to the old board object possibly be coming from?

As far as I can tell, the only variable that could be holding a reference directly or indirectly to ChessBoard objects is the board variable in the Play function in Program.cs. Everything else is using pure .NET framework types that can't reference my arbitrary types.

BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146
  • Is there any reason you feel you need to call `GC.Collect()`? The program's memory usage not going down does not indicate a need to call GC.Collect() nor does it necessarily indicate a memory leak. It's best to let the GC run when needed in *almost* all circumstances. – Bryan Crosby Dec 29 '14 at 19:08
  • It was only a debugging measure. I wanted the generation of wr to be reported immediately so i could understand if a memory leak existed. Apparently one does because it's in the second generation instead of gone after GC. Collect. – BlueMonkMN Dec 29 '14 at 19:09
  • Is the program experiencing a noticeable slowdown or did you just view Windows Task Manager? – Bryan Crosby Dec 29 '14 at 19:11
  • It's slow, but I don't think it's due to memory usage. I just fear that there will be memory problems if I play a full game of chess at a high level of intelligence. After just a few moves, the memory usage (Task Manager) grew about 200 MB per move to over a gigabyte. I know that the .NET memory manager is supposed to be pretty smart, but I don't want to learn the hard way that there's a memory leak, so I'm trying to analyze it in advance of an actual problem. VS profiler told me there were 200,000+ of these objects locked in memory for some reason so I'm taking a look at just one of them. – BlueMonkMN Dec 29 '14 at 19:23

1 Answers1

2

After reading What is a "rooted reference"? I found that the rooting of references can be different in debug mode than in release mode. I found that all the memory was returned when I ran in release mode without a debugger attached. It also ran significantly faster in release mode. I've always expected some difference between release and debug mode, but this is the first time I've seen such a significant difference in both memory usage and performance.

Community
  • 1
  • 1
BlueMonkMN
  • 25,079
  • 9
  • 80
  • 146