【问题标题】:Sequential vs parallel solution memory usage顺序与并行解决方案内存使用情况
【发布时间】:2015-06-26 09:56:00
【问题描述】:

我对以下场景有一个小问题: 我得到了一个 ID 值列表,我需要运行一个 SELECT 查询(其中 ID 是一个参数),然后将所有结果集合并为一个大的结果集并将其返回给调用者。

由于每个 ID 的查询可能会运行几分钟(这是另一个问题,但目前我认为这是一个既定事实),并且输入中可能有 1000 个 ID)我尝试使用任务。通过这种方法,我体验到内存使用缓慢但稳定的增长。

作为测试,我也做了一个简单的顺序解决方案,这有正常的内存使用图,但正如预期的那样,非常慢。运行时会有所增加,但完成后一切都会恢复到正常水平。

这是代码的骨架:

public class RowItem
{
    public int ID { get; set; }
    public string Name { get; set; }
    //the rest of the properties
}


public List<RowItem> GetRowItems(List<int> customerIDs)
{
    // this solution has the memory leak
    var tasks = new List<Task<List<RowItem>>>();
    foreach (var customerID in customerIDs)
    {
        var task = Task.Factory.StartNew(() => return ProcessCustomerID(customerID));
        tasks.Add(task);
    }

    while (tasks.Any())
    {
        var index = Task.WaitAny(tasks.ToArray());
        var task = tasks[index];
        rowItems.AddRange(task.Result);
        tasks.RemoveAt(index);
    }

    // this works fine, but slow
    foreach (var customerID in customerIDs)
    {
        rowItems.AddRange(ProcessCustomerID(customerID)));
    }

    return rowItems;
}

private List<RowItem> ProcessCustomerID(int customerID)
{
    var rowItems = new List<RowItem>();
    using (var conn = new OracleConnection("XXX"))
    {
        conn.Open();
        var sql = "SELECT * FROM ...";
        using (var command = new OracleCommand(sql, conn))
        {
            using (var dataReader = command.ExecuteReader())
            {
                using (var dataTable = new DataTable())
                {
                    dataTable.Load(dataReader);
                    rowItems = dataTable
                               .Rows
                               .OfType<DataRow>()
                               .Select(
                                   row => new RowItem
                                   {
                                       ID = Convert.ToInt32(row["ID"]),
                                       Name = row["Name"].ToString(),
                                       //the rest of the properties
                                   })
                               .ToList();
                }
            }
        }
        conn.Close();
    }
    return rowItems;
}

使用任务时我做错了什么?根据this MSDN article,我不需要手动处理它们,但几乎没有其他东西。我猜 ProcessCustomerID 没问题,因为它在两种变体中都被调用。

更新 为了记录当前的内存使用情况,我使用了Process.GetCurrentProcess().PrivateMemorySize64,但我在任务管理器>>进程中注意到了问题

【问题讨论】:

  • 我认为你应该结合顺序和并行的方法,通过将任务的数量限制为系统中的核心数量,每个任务顺序处理多个等于 IdsCount / CoresCount 的 Id(调整除法余数!)。
  • 我认为核心限制没有任何作用;这些是 IO 绑定的。这可能与 Oracle 驱动程序没有放弃内存有关。您不必从列表中删除或处置它们。
  • @Szeki:你是如何测量内存使用情况的?

标签: c# .net oracle memory-leaks task


【解决方案1】:

使用实体框架,您的 ProcessCustomerID 方法可能如下所示:

List<RowItem> rowItems;
using(var ctx = new OracleEntities()){
  rowItems = ctx.Customer
    .Where(o => o.id == customerID)
    .Select(
      new RowItem
      {
        ID = Convert.ToInt32(row["ID"]),
        Name = row["Name"].ToString(),
        //the rest of the properties
      }
    ).ToList();
}
return rowItems;

除非您要传输大量数据,例如图像、视频、数据或 blob,否则这应该是近乎瞬时的,结果是 1k 数据。

如果不清楚什么需要时间,并且您使用的是 10g 之前的 oracle,那么监控这将非常困难。但是,如果您使用实体框架,您可以将监控附加到它! http://www.hibernatingrhinos.com/products/efprof

至少一年前,Oracle 支持实体框架 5。

它们按顺序一个接一个地执行,并行它们实际上是在同一时间开始的,消耗您的资源并造成死锁。

【讨论】:

  • 即使在 PL/SQL Developer 中,查询本身也会运行几分钟。我可以使用 EF 并导入所有涉及的视图,然后我可以用 c# 重写整个逻辑。但我更感兴趣的是弄清楚为什么顺序执行正常工作,而带有任务的版本会导致内存使用量不断增加。或者您认为整个问题的根源在于我检索数据的方式?
【解决方案2】:

我认为您没有任何证据表明并行执行中存在内存泄漏。

可能是垃圾收集发生在不同的时间,这就是为什么经历了两种不同的读数。你不能指望它实时释放内存。 .Net 垃圾收集仅在需要时进行。看看“Fundamentals of Garbage Collection

任务管理器或Process.GetCurrentProcess().PrivateMemorySize64 可能不是很准确的方法来查找内存泄漏。如果这样做,至少要确保调用完整的垃圾回收并在读取内存计数器之前等待挂起的终结器。

GC.Collect();
GC.WaitForPendingFinalizers();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 2013-02-21
    • 1970-01-01
    • 2019-09-08
    • 2014-01-09
    相关资源
    最近更新 更多