【问题标题】:Memory consumption when initializing object初始化对象时的内存消耗
【发布时间】:2014-10-31 17:49:51
【问题描述】:

我正在尝试构建一些对象并将它们插入数据库。必须插入的记录数量很大~数百万。 插入是分批完成的。 我遇到的问题是我需要初始化新对象以将它们添加到列表中,最后,我将批量插入到列表的数据库中。因为我正在初始化大量对象,所以我的计算机内存(RAM)被填满了,它有点冻结了一切。 问题是 : 从内存的角度来看,我应该初始化将它们设置为 null 的对象吗? 此外,我正在尝试使用相同的对象引用。我做得对吗?

代码:

QACompleted completed = new QACompleted();
QAUncompleted uncompleted = new QAUncompleted();
QAText replaced = new QAText();

foreach (QAText question in questions)
{
    MatchCollection matchesQ = rgx.Matches(question.Question);
    MatchCollection matchesA = rgx.Matches(question.Answer);

    foreach (GetKeyValues_Result item in values)
    {

        hasNull = false;
        replaced = new QAText();  <- this object

        if (matchesQ.Count > 0)
        {
            SetQuestion(matchesQ, replaced, question, item);
        }
        else
        {
            replaced.Question = question.Question;
        }

        if (matchesA.Count > 0)
        {
            SetAnswer(matchesA,replaced,question,item);
        }
        else
        {
            replaced.Answer = question.Answer;
        }

        if (!hasNull)
        {
            if (matchesA.Count == 0 && matchesQ.Count == 0)
            {
                completed = new QACompleted();    <- this object
                MapEmpty(replaced,completed, question.Id);

            }
            else
            {
                completed = new QACompleted();  <- this object
                MapCompleted(replaced, completed, question.Id, item);
            }


            goodResults.Add(completed);
        }
        else
        {
            uncompleted = new QAUncompleted();     <- this object
            MapUncompleted(replaced,uncompleted,item, question.Id);

            badResults.Add(uncompleted);
        }
    }
    var success = InsertIntoDataBase(goodResults, "QACompleted");
    var success1 = InsertIntoDataBase(badResults, "QAUncompleted");
}

我已经标记了这些对象。我应该像replaced = NULL一样调用它们,还是应该使用构造函数? new QAText() 和 = null 有什么区别?

【问题讨论】:

  • 我只是想格式化它,它告诉我你已经完成了。谢谢你:)
  • 您的goodResultsbadResults 列表的大小正在增加。无论您是在循环内部还是外部实例化变量,您仍然会在这些列表中保留引用。
  • 我在将列表插入数据库时​​清空列表
  • 这里没有显示,也没有显示实例化 goodResultsbadResults 列表的位置。

标签: c# memory memory-management


【解决方案1】:

创建对象的内存消耗

在 C# 中创建对象总是需要内存成本。这与对象的内存布局有关。假设您使用的是 64 位操作系统,运行时必须为同步块分配额外的 8 个字节,为方法表指针分配 8 个字节。在同步块和方法表指针之后是您自定义的数据字段。除了不可避免的 16 字节标头之外,对象总是与 8 字节的边界对齐,因此会产生额外的开销。

如果您确切知道您创建的对象数量,您可以粗略估计内存开销。但是,我建议您在假设您的内存压力来自对象布局开销时要小心。这也是我建议您将开销估算为第一步的原因。您最终可能会意识到,即使可以神奇地完全消除布局开销,您也不会在内存性能方面产生巨大差异。毕竟,对于一百万个对象,对象头的开销只有 16 MB。

replaced = new QAText() 和replaced = null 的区别

我想在你设置 replace 为 null 之后你还需要创建另一个 QAText() 吗?如果是这样,那么在内存方面,垃圾收集器没有真正的区别。如果您没有对它进行任何其他引用,旧的 QAText 实例将被收集。然而,何时收集实例是垃圾收集器的调用。做replaced = null不会让GC更早发生。

您可以尝试重复使用相同的 QAText 实例,而不是每次都创建一个新实例。但是每次都创建一个新的,不会造成很高的内存压力。它会使 GC 更忙一些,从而导致更高的 CPU 使用率。

找出高内存使用的真正原因

如果您的应用程序确实使用了大量内存,则必须查看 QACompleted 和 QAUncompleted 对象的设计。这些是添加到列表中并占用内存的对象,直到您将它们提交到数据库。如果这些对象设计得很好(它们只占用它们必须占用的内存),正如 Peter 指出的那样,您应该使用较小的批量大小,这样您就不必在内存中保留太多它们。

您的程序中还有其他因素可能导致意外的内存使用。 goodResults 和 badResults 的数据结构是什么?它们是 List 还是 LinkedList?内部列表只不过是一个动态数组。它使用增长策略,当它满了时,它的大小总是会加倍。 always-double 策略会很快耗尽内存,尤其是当您有很多条目时。

另一方面,LinkedList 不存在上述问题。但是每个单个节点需要大约 40 个额外的字节。

还值得检查 MapCompleted 和 MapUnCompleted 方法在做什么。他们是否对replaced 对象进行了长期引用?如果是这样会导致内存泄漏。

总而言之,在处理内存问题时,您应该关注宏观范围的问题,例如数据结构的选择或内存泄漏。或者优化您的算法,这样您就不必一直将所有数据保存在内存中。

【讨论】:

  • QACompleted 和 QAUNcompleted 是包含 5 个字符串和 1 个长属性的简单类。好坏结果都是简单的List。
  • 您能否更具体地了解字符串的大小以及两个列表的长度?根据你目前的统计数据,感觉你有记忆问题是不正常的
【解决方案2】:

实例化新的(尽管是空的)对象总是需要一些内存,因为它必须为对象的字段分配空间。如果您不打算访问或设置实例中的任何数据,我认为创建它没有任何意义。

【讨论】:

  • 我正在为每个对象分配数据。我认为如果我在 foreach 循环之前声明对象,它将使用相同的引用并覆盖旧实例。这是真的 ?我该怎么做?
  • 您不能用另一个对象的数据覆盖一个对象的数据。在循环内部或外部使用 QAText replaced; 没有任何效果,它只是一个变量(当它被编译时,变量的位置无关紧要)。如果你对整个方法使用相同的实例,它的数据将被覆盖。如果您希望列表中有很多非空对象,您可以将 QAText 更改为结构。
【解决方案3】:

不幸的是,代码示例没有写得更好。代码中似乎遗漏了很多声明,以及未记录的副作用。这使得提供具体建议变得非常困难。

也就是说……

您的replaced 对象似乎不会在循环的一次迭代之后保留,因此它不是问题的一部分。 completeduncompleted 对象被添加到列表中,因此它们确实会增加您的内存消耗。同样,goodResultsbadResults 列出了它们自己(它们的声明在哪里?)。

如果您使用的是 RAM 太少的计算机,那么是的……您会遇到性能问题,因为 Windows 使用磁盘来弥补 RAM 的不足。即使有足够的 RAM,在某些时候您可能会遇到 .NET 在对象大小方面的限制(即您只能将这么多元素放入一个列表中)。因此,无论哪种方式,您似乎都需要减少峰值内存使用量。

您说过,当列表中的数据插入数据库时​​,列表会被清除。所以大概这意味着values 列表中有太多元素(代码示例中未声明、未记录的变量之一),列表及其对象在到达内部循环末尾并插入数据进入数据库。

在这种情况下,解决该问题的最简单方法似乎是在内部foreach 循环内部分批提交更新。例如。在该循环的末尾,添加如下内容:

if (goodResults.Count >= 100000)
{
    var success = InsertIntoDataBase(goodResults, "QACompleted");
}

if (badResults.Count >= 100000)
{
    var success = InsertIntoDataBase(badResults, "QACompleted");
}

(当然将实际截断声明为命名常量,并酌情处理数据库插入结果返回值)。

当然,你也可以在外循环的末尾插入。

【讨论】:

  • 列表不超过 50k。我有一台 16GB 内存的电脑。您想查看哪部分代码,以便我更新我的问题。
  • "50K" 以 50 KB 为单位?如果是这样,那么您就不可能遇到内存消耗问题。如果您的意思是“五万个项目”,那么也许您需要将批量大小设置得更小(因为显然对象本身很大)。至于您应该发布“代码的哪一部分”,这是一个错误的问题。您实际上不应该发布 real 代码;您应该构建一个良好、简洁、完整的代码示例来演示相同的问题。见stackoverflow.com/help/mcve
猜你喜欢
  • 2016-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-05
  • 1970-01-01
  • 1970-01-01
  • 2020-02-12
  • 1970-01-01
相关资源
最近更新 更多