POCO列表的OutOfMemory异常

时间:2017-02-24 21:55:04

标签: c# memory

鉴于此代码:

public class Customer
{
    public int CustomerID { get; set; }
    public string Name { get; set; }
    public List<Qualification> Qualifications { get; set; }
}

public class Qualification
{
    public QualificationType QualificationType { get; set; }
    public decimal Value { get; set; }
}

public class Action
{
    public ActionID { get; set; }
    public int CustomerID { get; set; }
    public decimal ActionValue { get; set; }
}

public class Service : IService
{
    public List<Customer> ProcessCustomers()
    {
        List<Customer> customers = _customerService.GetCustomers(); // 250,000 Customers
        List<Action> actions = _actionService.GetActions(); // 6,000

        foreach (var action in actions) {
            foreach (affectedCustomer in customers.Where(x => x.CustomerID < action.CustomerID)) {
                affectedCustomer.Qualifications.Add(new Qualification { QualificationType = QualificationType.Normal, Value = action.ActionValue});
            }

            foreach (affectedCustomer in customers.Where (x => SpecialRules(x))) {
                affectedCustomer.Qualifications.Add(new Qualification { QualificationType = QualificationType.Special, Value = action.ActionValue});
            }
        }
    }
}

“最合格”的客户最终可能获得12,000项资格。平均而言,客户最终可能获得约100个资格。

但是,在处理了大约50个动作之后,我很早就得到了OOME。那时,我的名单中仍然只有250,000名客户,但整个客户中已经有大约5,000,000个资格。

那是很多吗?对我来说似乎有点平淡无奇。我怀疑我可以拥有数以千万计的客户,每个客户平均拥有1000个资格,而且还可以。我甚至都不接近。

在代码中,我可以做些什么来提高效率?我意识到我可以将每个(或大量组)Actions的结果写入数据库,但在写入结果之前,我宁愿尽可能多地在内存中进行操作。

这样做是循环通过6,000个操作,并且对于每个操作,为一些可变数量的客户添加资格。对于每个操作,具有customerID&gt; = Action-Causing客户的所有客户都将添加Qualification。这就是大约12亿条记录。此外,对于每个操作,8-10个客户都会收到资格认证。与12亿美元相比,只有60,000条记录。

我试图在内存中执行此操作,因为我不想将数十亿条记录插入到数据库中。我将需要这个记录分离用于下一步处理,它从上到下查看客户资格和客户ID的步骤差异。虽然最后,我最终在数据库中放置了结果(比SUM更复杂)。但我只能通过查看个人资格差异的步骤来实现这些结果,例如曲线上的评分。

2 个答案:

答案 0 :(得分:1)

您下载的对象数量非常庞大 - 您应该考虑以较小的块处理数据,而不是一次性下载所有数据。

在.NET中,单个对象有a limit of memory - 永远不允许创建超过2 GiB的单个对象。对于.NET 4.5,对于数组,它在64位上为has been lifted

列表将数据存储在数组中。如果要将所有数据下载到一个列表中,则基础数组的大小超出限制,并且您具有OutOfMemory异常。

答案 1 :(得分:0)

我一直在宣传SOLID Code和显式域模型的重要性。我没有被迫编写域逻辑,你需要在几年内考虑数十万个数据点。这是我发现的关于.NET OOME的内容:

  1. 对象集合不是指向对象的指针集合。集合本身就是其各个部分的总和。
  2. 对于32位应用程序,应用程序可以使用~2GiB。因此,即使您将大型集合拆分为较小的集合集,您也无法查看大量数据集。
  3. 对象没有静态地址。 .Net可以自由移动对象,除非您创建代码unsafe并强制对象变粘。但即使你这样做,单个对象仍然受到~2GiB最大尺寸(这很好),并且应用程序仍然需要~2GiB最大内存。因此,创建指针集合不是一种选择。
  4. Web应用程序(Web API和ASP.Net)不能使用IMAGE_FILE_LARGE_ADDRESS_AWARE标志,或者从我能说的内容中轻松运行64个大应用程序,我希望听到其他情况。
  5. 不幸的解决方案

    我需要打破我的域模型并做一些黑客攻击。例如:我必须有一个Customer类,而不是我可以自由计算的资格列表和总和:

    public class Customer
    {
        public int CustomerID { get; set; }
        public string Name { get; set; }
        public decimal QualificationType1WithVariableType1Total { get; set; }
        public decimal QualificationType1WithVariableType2Total { get; set; }
        public decimal QualificationType2WithVariableType1Total { get; set; }
        public decimal QualificationType2WithVariableType2Total { get; set; }
    }
    

    事先有效地完成所有计算,如果我引入其他变量,我将需要使用“Total”变量。这样做意味着;客户只有半打预先计算的字段,而不是向客户添加数千条记录,而后来我可以在计算中使用这些字段。

    所以我能够减少我的内存占用,但是我不再能够明确地使用我的域并且在观察大量结果时自由地进行计算。

    当然,这些属性在技术上已经存在了。有些是Readonly,并根据计数,平均值和总和执行LINQ特殊方程。有些是基于线性链上下100个CustomerID内的其他客户的进展读/写。但相反,我必须抛弃所有上下文并仅使用总计。

    我很沮丧,在这个时代,我必须打破我的上下文域模型才能在硬件约束下工作。我的应用程序的速度非常快,并且已经在O(1)附近缩放,因此速度不是问题。