【问题标题】:Available Memory for a process进程的可用内存
【发布时间】:2012-12-05 16:02:39
【问题描述】:

我正在尝试获取进程中的可用内存,以确保我不会收到 OutOfMemoryException。我在互联网上搜索并找到了几个如何获取内存已使用但不可用的示例。

让我提供用例...

我有一个正在执行批量插入的进程(使用 SqlBulkCopy)。我将DataTable 传递给WriteToServer 方法。我不能使用DataReader,因为我必须能够在失败时重试该过程。我的第一个想法是一次选择任意数量的行插入,比如 50,000。但这是一个不知道数据的通用过程;它不知道列数,也不知道每行中的数据量。所以我想我可以在向DataTable 添加行时监控内存,然后在内存快用完时将其发布到SqlBulkCopy

这是一种有效的方法还是有更好的方法?
如果这是一种有效的方法,我将使用什么函数来确定可用内存量?

到目前为止,这是我的代码...AvailableMemoryIsLow 是我无法确定的。

// m_buffer is a read-once cache (implements IDataReader) that pulls 
// data from an external source as needed so it uses very little memory.
// My original implementation just used m_buffer as the parameter of 
// WriteToServer but now I have to add retry logic into the process.

DataTable dataTable = new DataTable(m_tableName);
foreach (DataField d in m_buffer.GetColumns())
    dataTable.Columns.Add(new DataColumn(d.FieldName, d.FieldType));

while (m_buffer.Read())
{
    DataRow row = dataTable.NewRow();
    for (int i = 0; i < m_buffer.FieldCount; i++)
        row[i] = m_buffer.GetValue(i);

    dataTable.Rows.Add(row);

    // How do I determine AvailableMemoryIsLow
    if (rowCount++ >= 50000 || AvailableMemoryIsLow)
    {
        PutDataIntoDatabase(dataTable);
        dataTable.Clear();
        rowCount = 0;
    }
}

if (dataTable.Rows.Count > 0)
    PutDataIntoDatabase(dataTable);

【问题讨论】:

    标签: c# c#-4.0


    【解决方案1】:

    很明显,您是在 32 位机器上运行此代码,否则您不会遇到此问题。一般来说,推动程序消耗几乎所有可用的虚拟内存空间(2 GB)并不是一个合理的做法。除了永远存在的 OOM 危险之外,您正在处理的数据类型是“实时数据”,它很可能会映射到 RAM。需要几乎所有可用 RAM 的程序对该程序、操作系统和在该机器上运行的其他进程的运行非常不利。

    您强制操作系统开始选择如何在进程需要的内存和为文件系统缓存保留的内存之间分配内存。这种选择最终总是迫使数据从 RAM 进入页面文件。这会大大减慢操作速度,无论是在写入时还是在进程需要它返回 RAM 时。称为“抖动”的操作系统性能问题。

    只是不要这样做,在 RAM 中存储如此多的数据不会使您的程序更快。它使它变慢。在 32 位操作系统上消耗的 RAM 量的合理上限徘徊在 500 兆字节附近。没有任何需要完全达到这个限制,计算行数就足够了。

    【讨论】:

    • 我面临的挑战是我不知道数据。每行都可能占用数兆字节的内存,因此选择正确的行数对我来说是一个挑战。我只是猜测我选择的任何数字都会有一个最终超过这个数字的客户。
    • 我还没买,单行消耗数兆字节的数据是一个相当严重的数据库设计问题。最终会有代码填充一行的列,当然你也可以添加计数。
    • 经过深思熟虑,考虑到所有这些意见,并与我的同事讨论后,我认为我确实过度分析了问题并使其过于复杂。我最终计算了存储在 .config 文件中的最大行数的行数,默认值为 50,000。感谢您的所有意见。
    【解决方案2】:

    您提到您找到了告诉您分配了多少内存的方法

        GC.GetTotalMemory(false);
    

    就是这样一种方法(我想你已经找到了)。

    我想从 MSDN 文档中指出一件事。

    检索当前认为分配的字节数

    这是 GC.GetTotalMemory 方法文档的最顶部。我想在上面的短语中指出thought这个词。现在我知道您知道如何找到问题中提到的分配数量,但是我提出这一点是为了说明 C# 是一种托管语言。内存使用和消耗从您那里抽象出来,甚至 GC 方法也只是为了让您对进程中发生的事情有一个模糊的了解。对我来说,手动处理内存级别听起来既冒险又不可靠。

    我建议您使用原始方法,但将批量大小拉回一个水平,这样无论您使用多少列,您都极不可能出现内存不足异常。想想几百,也许几千,而不是几万。即使您尝试检测到这些级别的内存问题风险,您通过较大批次获得的任何性能提升都可能超过。另一个答案中提到的性能工具将是确定批量大小应该是多少以及是否存在问题的好方法。

    【讨论】:

    • 这是一个非常好的观点。在某些极端情况下,一行可能包含大量内容,因此即使一次将其设置为 1000 行也会导致 OutOfMemoryException,但让我考虑一下。也许应该忽略极端的极端情况。如果 .Net 在内存耗尽之前提供某种方式来“警告”你,那肯定会很好......但我明白为什么不能/不能。
    • @GeneS:在考虑了 Hans 的回答之后,我认为最好将批量大小回滚到这样一个水平,即所有合理的情况甚至都不会接近可能导致 OOM 的内存消耗水平例外。如果您有大量内存消耗,更不用说 OOM 异常,批量插入可能不值得。至少,批次可能需要比以前想象的要严格得多。同样,很好地使用了 paulsm4 提到的分析工具。
    【解决方案3】:

    问题是有很多不同种类的“资源”;其中任何一个都可以通过“OutOfMemoryException”表现出来。

    也许你最好的选择是GC.GetTotalMemory(false)

    更好的方法是获得像 JetBrains dotTrace 或 RedGate ANTS 这样的工具。

    恕我直言...

    PS:

    如果您正在执行 SQL 批量复制,请务必设置 EnableStreaming:

    【讨论】:

    • dotTrace 或 RedGate 能做什么?此代码需要在生产中运行。此外,GC.GetTotalMemory 不会告诉我available 内存的数量。
    • 我的意思是没有一个单一的“神奇数字”可以帮助你——事情比这更复杂。您要做的最好的事情(对于 .Net 程序)可能是“GC.GetTotalMemory(false)”。连同Process.WorkingSet64, Process.VirtualMemory64(对不起,我没有提到)。在这里查看更多信息:stackoverflow.com/questions/3021714/…
    • 如果内存是一个问题,老实说,我认为你最好的办法是让自己熟悉一个好的工具(比如 ANTS 和/或 dotTrace;比如早期的 Rational Purify)并对你的程序进行压力测试——以识别任何潜在的泄漏、瓶颈或热点。恕我直言...
    猜你喜欢
    • 1970-01-01
    • 2015-11-04
    • 2014-08-05
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    相关资源
    最近更新 更多