从 C# 代码测量本机 DLL 内存使用情况

Measure native DLL memory usage from c# code

我正在使用供应商提供的 C++ DLL,我用 DLLImport 调用它来解析和处理包含许多对象类型的文件。

我需要在文件中的对象数量和内存使用之间建立关联,以便(希望)能够防止有时发生的 OutOfMemoryExceptions。


更新

为了更清楚地说明我要测量的内容和原因:内存不足异常是预料之中的,因为一些非常复杂的文件需要占用高达 7gb 的内存来加载(由 perfmon 测量):它们是有时巨大而复杂的建筑物的 3D 地图,从墙壁到各个螺丝和螺栓,包括外面的树木和每个房间的桌椅。

并且由于 DLL 可以并行加载多个地图(它在网络服务器上并且进程是共享的),加载 2x 7gb 文件可以理解地在具有 8gb RAM 的机器上触发 OutOfMemoryException。
不过7gb的还是比较少见的,大部分地图都在500mb左右,有的1~2gb。

我们真正需要的是不是发现内存泄漏(还...),而是能够在加载文件之前知道[=42] =] 它可能会使用多少内存。因此,当用户尝试加载一个文件时,我们计算可能需要大约 2gb 的 RAM,而机器有 1gb 空闲,我们会做一些事情;从在 Azure 中启动一个新的 VM 到阻止用户工作,我们还不知道是什么,但我们不能让 DLL 每次都让整个服务器崩溃。

为了做到这一点,我想找出,例如,"the DLL uses 1mb of memory for each 100 geometry object"。


所以我有一堆文件要测试(大约一百个),我想按顺序加载它们,测量原生DLL的内存使用(之前和之后),卸载文件,处理下一个。然后我得到一个包含所有数据的漂亮 CSV 文件。

我试过System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64但它只给我当前进程的内存,但DLL似乎不存在于当前进程中,因为大多数措施给我0字节(之前和之后的差异文件加载)。
我也试过GC.GetTotalMemory()但也好不了多少,文件貌似都是1080字节

private static void MeasureFilesMemoryUsage(string[] files) {
    foreach (var file in files) {
        var beforeLoad = MeasureMemoryUsage();

        wrapper.LoadFile(file)

        var afterLoad = MeasureMemoryUsage();

        wrapper.Unload();

        // save beforeLoad and afterLoad
    }
}

private static long MeasureMemoryUsage() {
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    return System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64;
}

我知道像 VMMAP 或 RedGate Ants Memory Profiler(或简单的性能计数器)这样的工具,但这些工具不允许我将内存使用情况与特定加载的文件相匹配,我必须一个一个地加载文件,暂停程序,在工具中进行测量,然后记下结果。我不想对 100 个文件执行此操作。

如何测量 .Net 代码中特定 C++ DLL 的内存使用情况?

阅读@HansPassant 评论后,我将我的测试分为 2 个程序:一个加载文件,一个读取第一个程序的内存测量值。
在这里,清理它们以删除其他措施(例如我的 json 文件中的项目数)和结果保存。

"measures" 程序:

public static void Main(string[] args) {
    foreach (var document in Directory.EnumerateDirectories(JsonFolder)) {
        MeasureMemory(document);
    }
}

private static void MeasureMemory(string document) {
    // run process
    var proc = new Process {
        StartInfo = new ProcessStartInfo {
            FileName = "loader.exe",
            Arguments = document,
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = true
        }
    };
    proc.Start();

    // get process output
    var output = string.Empty;
    while (!proc.StandardOutput.EndOfStream) {
        output += proc.StandardOutput.ReadLine() + "\n";
    }

    proc.WaitForExit();

    // parse process output
    var processMemoryBeforeLoad = long.Parse(Regex.Match(output, "BEFORE ([\d]+)", RegexOptions.Multiline).Groups[1].Value);
    var processMemoryAfterLoad = long.Parse(Regex.Match(output, "AFTER ([\d]+)", RegexOptions.Multiline).Groups[1].Value);

    // save the measures in a CSV file
}

和 "loader" 程序:

public static int Main(string[] args) {
    var document = args[0];
    var files = Directory.EnumerateFiles(document);

    Console.WriteLine("BEFORE {0}", MeasureMemoryUsage());

    wrapper.LoadFiles(files);

    Console.WriteLine("AFTER {0}", MeasureMemoryUsage());

    wrapper.Unload();

    return 0;
}

private static long MeasureMemoryUsage() {
    // make sure GC has done its job
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    return System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64;
}