具有许多C ++ Interop调用的自动C#垃圾收集器性能较差

时间:2014-10-08 21:08:37

标签: c# c++ .net garbage-collection interop

什么可能导致C#垃圾收集在我的C#应用​​程序中如此糟糕地失败,当一个放置良好的GC.Collect解决问题时执行大量的C ++调用?我的C#app使用System.Runtime.InteropServices DllImport和CallingConvention.Cdecl进行数百万次C ++调用,并有一些C#析构函数来释放一些C ++非托管内存。我正在使用.NET Framework 4。

  1. 什么可能导致我的应用程序中的以下代码强制进行分页,从而减慢执行速度(在我的32 GB系统上消耗29 GB的RAM并且在我杀死进程之前花费超过4分钟) ManualGCtrue的内存使用量大约为600MB,执行在29秒内完成?

  2. 为什么离开ManualGC false并将Write更改为true将内存使用量限制为大约12 GB,并允许执行在大约59秒内完成而不进行分页

  3. 我的应用程序中的一些代码片段(显然有些名称已更改):

    private static int callCount = 0;
    private const bool ManualGC = false;
    private const bool Write    = false;
    
    internal static void CommonlyCalled()
    {
        ++callCount;
    
        if ( callCount % 100000 == 0)
        {
            if (ManualGC)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            if (Write) Console.WriteLine(HandleErrorsCallCount);
        }
    
        DoLogic();
    }
    

    使用Windows任务管理器中的“内存(专用工作集)”列测量内存。行为始终是可重复的。

2 个答案:

答案 0 :(得分:7)

  

并且有一些C#析构函数来释放一些C ++非托管内存

如果您的包装器很小并且您的C ++代码需要大量内存,那还不够。你只是没有给GC施加足够的压力让它尽快给你的终结者打电话。实现IDisposable来解决这个问题是样板。但不是一个完整的解决方案,你应该告诉GC,以便它可以做些什么。一些代码可以使用:

using System;
using System.Runtime.InteropServices;

class Program {
    static void Main(string[] args) {
        while (!Console.KeyAvailable) {
            new Wrapper();
        }
    }
}

class Wrapper {
    private const int alloc = 10 * 1024;    // C++ object memory usage
    private readonly bool useamp = false;   // Change this after testing
    private IntPtr mem;

    public Wrapper() {
        if (useamp) GC.AddMemoryPressure(alloc);
        mem = Marshal.AllocHGlobal(alloc);
    }
    ~Wrapper() {
        Marshal.FreeHGlobal(mem);
        if (useamp) GC.RemoveMemoryPressure(alloc);
    }
}

运行此程序时,请注意该程序的内存使用情况。在我的机器上,私有字节构建大约半个演出。现在更改useamp并再次运行,您会发现更多效率更高,只需要4 MB。无需调用Dispose :)在Win 8.1,.NET 4.5.1上测试,在较旧的.NET版本上可能会得到非常不同的结果。

您为 alloc 选择的值并不重要,它只需要在球场。显然你确实需要超过10KB。

答案 1 :(得分:1)

听起来您依赖于用户定义的终结器来在C#应用程序中正确行为。您需要更新C#代码以解决此方案中的以下特定问题:

  1. 您不应该使用用户定义的终结器 1 (一种C#方法,其语法类似于C ++析构函数)。如果您有一个需要清理的非托管资源,请创建一个扩展SafeHandle的类,以便只包含资源的句柄。
  2. 您的代码应该写在Dispose()实例上,总是显式调用SafeHandle,当您完成使用它们时,这些实例将包装您的非托管资源。永远不要依赖C#中的终结器来执行清理操作。
  3. 1 这句话足以说明以下几点是生活的规则:

      

    如果你用C#编写用户定义的终结器,你可能做错了。