C#OutOfMemory,映射内存文件或临时数据库

时间:2012-02-26 04:06:45

标签: c# database out-of-memory memory-mapped-files large-data

寻求一些建议,最佳实践等......

技术:C#.NET4.0,Winforms,32位

我正在寻求一些关于如何在我的C#Winforms应用程序中最好地处理大数据处理的建议,该应用程序经历了高内存使用(工作集)和偶尔的OutOfMemory异常。

问题是当打开“购物篮”时,我们会在“内存中”执行大量数据处理。 以简单的术语加载“购物篮”时,我们执行以下计算;

  1. 对于“购物篮”中的每件商品,检索其历史价格一直回到商品首次出现在库存的日期(可能是两个月,两年或二十年的数据)。历史价格数据通过互联网从文本文件中检索,价格插件支持任何格式。

  2. 对于每件商品,自首次出现库存以来的每一天都会计算各种指标,这些指标会为购物篮中的每件商品构建历史档案。

  3. 结果是我们可以根据“购物篮”中的项目数量执行数百,数千和/或数百万次计算。如果购物篮包含太多商品,我们就有可能遇到“OutOfMemory”异常。

    一些警告;

    1. 需要为“购物篮”中的每个项目计算此数据,并保留数据直至“购物篮”关闭。

    2. 即使我们在后台线程中执行步骤1和2,速度也很重要,因为“购物篮”中的项目数量会极大地影响整体计算速度。

    3. 当“购物篮”关闭时,.NET垃圾收集器会挽救内存。我们已经分析了我们的应用程序,并确保在关闭一个篮子时正确处理和关闭所有参考文件。

    4. 完成所有计算后,结果数据存储在IDictionary中。 “CalculatedData是一个类对象,其属性是由上述过程计算的单个度量。

    5. 我想过的一些想法;

      显然我主要关心的是减少计算所使用的内存量,但是如果我用的话,只能减少使用的内存量 1)减少每天计算的指标数量或
      2)减少用于计算的天数。

      如果我们希望满足业务需求,这两种选择都是不可行的。

      • 内存映射文件
        一个想法是使用将存储数据字典的内存映射文件。这是可行/可行的,我们如何才能实现这一目标?

      • 使用临时数据库
        我们的想法是使用一个单独的(非内存中)数据库,该数据库可以为应用程序的生命周期创建。随着“购物篮”被打开,我们可以将计算出的数据保存到数据库中以供重复使用,从而减少重新计算同一“购物篮”的要求。

      我们应该考虑其他替代方案吗?在计算大数据并在RAM之外执行它们时,最佳做法是什么?

      任何建议都表示赞赏....

3 个答案:

答案 0 :(得分:0)

最简单的解决方案是数据库,也许是SQLite。内存映射文件不会自动成为字典,您必须自己编写所有内存管理代码,从而与.net GC系统本身争夺数据所有权。

答案 1 :(得分:0)

如果您对尝试内存映射文件方法感兴趣,可以立即尝试。我编写了一个名为MemMapCache的小型本机.NET包,它本质上创建了一个由MemMappedFiles支持的key / val数据库。这是一个hacky概念,但程序MemMapCache.exe保留对内存映射文件的所有引用,这样如果您的应用程序崩溃,您不必担心丢失缓存状态。

使用起来非常简单,你应该能够在代码中删除它而不需要太多修改。以下是使用它的示例:https://github.com/jprichardson/MemMapCache/blob/master/TestMemMapCache/MemMapCacheTest.cs

对于您来说,至少可以进一步弄清楚您需要为实际解决方案做些什么,这可能会有所帮助。

如果你最终使用它,请告诉我。我对你的结果感兴趣。

但是,长期来看,我推荐Redis。

答案 2 :(得分:0)

作为对这个线索的绊脚石的更新...

我们最终使用SQLite作为缓存解决方案。我们使用的SQLite数据库与应用程序使用的主数据存储区分开。我们将计算数据保存到SQLite(diskCache),因为它是必需的,并且有代码控制缓存失效等。这对我们来说是一个合适的解决方案,因为我们能够实现每秒大约100,000个记录的写入速度。

对于那些感兴趣的人,这是控制插入diskCache的代码。 此代码完全归功于JP Richardson (在此处回答了一个问题),因为他出色的博客文章。

internal class SQLiteBulkInsert
{
#region Class Declarations

private SQLiteCommand m_cmd;
private SQLiteTransaction m_trans;
private readonly SQLiteConnection m_dbCon;

private readonly Dictionary<string, SQLiteParameter> m_parameters = new Dictionary<string, SQLiteParameter>();

private uint m_counter;

private readonly string m_beginInsertText;

#endregion

#region Constructor

public SQLiteBulkInsert(SQLiteConnection dbConnection, string tableName)
{
    m_dbCon = dbConnection;
    m_tableName = tableName;

    var query = new StringBuilder(255);
    query.Append("INSERT INTO ["); query.Append(tableName); query.Append("] (");
    m_beginInsertText = query.ToString();
}

#endregion

#region Allow Bulk Insert

private bool m_allowBulkInsert = true;
public bool AllowBulkInsert { get { return m_allowBulkInsert; } set { m_allowBulkInsert = value; } }

#endregion

#region CommandText

public string CommandText
{
    get
    {
        if(m_parameters.Count < 1) throw new SQLiteException("You must add at least one parameter.");

        var sb = new StringBuilder(255);
        sb.Append(m_beginInsertText);

        foreach(var param in m_parameters.Keys)
        {
            sb.Append('[');
            sb.Append(param);
            sb.Append(']');
            sb.Append(", ");
        }
        sb.Remove(sb.Length - 2, 2);

        sb.Append(") VALUES (");

        foreach(var param in m_parameters.Keys)
        {
            sb.Append(m_paramDelim);
            sb.Append(param);
            sb.Append(", ");
        }
        sb.Remove(sb.Length - 2, 2);

        sb.Append(")");

        return sb.ToString();
    }
}

#endregion

#region Commit Max

private uint m_commitMax = 25000;
public uint CommitMax { get { return m_commitMax; } set { m_commitMax = value; } }

#endregion

#region Table Name

private readonly string m_tableName;
public string TableName { get { return m_tableName; } }

#endregion

#region Parameter Delimiter

private const string m_paramDelim = ":";
public string ParamDelimiter { get { return m_paramDelim; } }

#endregion

#region AddParameter

public void AddParameter(string name, DbType dbType)
{
    var param = new SQLiteParameter(m_paramDelim + name, dbType);
    m_parameters.Add(name, param);
}

#endregion

#region Flush

public void Flush()
{
    try
    {
        if (m_trans != null) m_trans.Commit();
    }
    catch (Exception ex)
    {
        throw new Exception("Could not commit transaction. See InnerException for more details", ex);
    }
    finally
    {
        if (m_trans != null) m_trans.Dispose();

        m_trans = null;
        m_counter = 0;
    }
}

#endregion

#region Insert

public void Insert(object[] paramValues)
{
    if (paramValues.Length != m_parameters.Count) 
        throw new Exception("The values array count must be equal to the count of the number of parameters.");

    m_counter++;

    if (m_counter == 1)
    {
        if (m_allowBulkInsert) m_trans = m_dbCon.BeginTransaction();
        m_cmd = m_dbCon.CreateCommand();

        foreach (var par in m_parameters.Values)
            m_cmd.Parameters.Add(par);

        m_cmd.CommandText = CommandText;
    }

    var i = 0;
    foreach (var par in m_parameters.Values)
    {
        par.Value = paramValues[i];
        i++;
    }

    m_cmd.ExecuteNonQuery();

    if(m_counter != m_commitMax)
    {
        // Do nothing
    }
    else
    {
        try
        {
            if(m_trans != null) m_trans.Commit();
        }
        catch(Exception)
        { }
        finally
        {
            if(m_trans != null)
            {
                m_trans.Dispose();
                m_trans = null;
            }

            m_counter = 0;
        }
    }
}

#endregion

}