Search code examples
c++.netgarbage-collectionswigdispose

Why calling GC.Collect is speeding things up


We have a C++ library (No MFC, No ATL) that provides some core functionality to our .NET application. The C++ DLL is used by SWIG to generate a C# assembly that can be used to access its classes/methods using PInvoke. This C# assembly is used in our .NET application to use the functionality inside C++ DLL.

The problem is related to memory leaks. In our .NET application, I have a loop in my .NET code that creates thousands of instances of a particular class from the C++ DLL. The loop keeps slowing down as it creates instances but if I call GC.Collect() inside the loop (which I know is not recommended), the processing becomes faster. Why is this? Calling Dispose() on the types does not have any affect on the speed. I was expecting a decrease in program speed on using GC.Collect() but it's just the opposite.

Each class that SWIG generates has a ~destructor which calls Dispose(). Every Dispose method has a lock(this) around the statements that make calls to dispose unmanaged memory. Finally it calls GC.SuppressFinalize. We also see AccessViolationException sporadically in Release builds. Any help will be appreciated.


Solution

  • Some types of object can clean up after themselves (via Finalize) if they are abandoned without being disposed, but can be costly to keep around until the finalizer gets around to them; one example within the Framework is the enumerator returned by Microsoft.VisualBasic.Collection.GetEnumerator(). Each call to GetEnumerator() will attach an object wrapped by the new enumerator object to various private update events managed by the collection' when the enumerator is Disposed, it will unsubscribe its events. If GetEnumerator() is called many times (e.g. many thousands or millions) without the enumerators being disposed and without an intervening garbage collection, the collection will get slower and slower (hundreds or thousands of times slower than normal) as the event subscription list keeps growing. Once a garbage-collection occurs, however, any abandoned enumerators' Finalize methods will clean up their subscriptions, and things will start working decently again.

    I know you've said that you're calling Dispose, but I have a suspicion that something is creating an IDisposable object instance and not calling Dispose on it. If IDisposable class Foo creates and owns an instance of IDisposable class Bar, but Foo doesn't Dispose that instance within its own Dispose implementation, calling Dispose on an instance of Foo won't clean up the Bar. Once the instance of Foo is abandoned, whether or not it has been Disposed, its Bar will end up being abandoned without disposal.