繁体   English   中英

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

[英]C# OutOfMemory, Mapped Memory File or Temp Database

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

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

我正在寻求一些有关如何最好地解决C#Winforms应用程序中大型数据处理问题的建议,该应用程序会遇到较高的内存使用率(工作集)和偶发的OutOfMemory异常。

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

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

  2. 对于每件商品,自从它第一次出现在库存中以来的每一天,都要计算各种指标,从而为购物篮中的每件商品建立历史档案。

结果是,根据“购物篮”中的项目数量,我们有可能执行数十万,/或数百万次计算。 如果购物篮中的物品太多,我们将冒碰到“ OutOfMemory”异常的风险。

一些注意事项 ;

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

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

  3. 当关闭“购物篮”时,.NET垃圾收集器将抢救内存。 我们已经分析了我们的应用程序,并确保在关闭购物篮时正确放置和关闭所有引用。

  4. 完成所有计算后,结果数据将存储在IDictionary中。 “ CalculatedData是一个类对象,其属性是通过上述过程计算出的各个指标。

我考虑过的一些想法;

显然,我主要关心的是减少计算使用的内存量,但是只有在以下情况下,才能减少使用的内存量:
1)减少每天要计算的指标数量,或者
2)减少用于计算的天数。

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

  • 内存映射文件
    一种想法是使用内存映射文件来存储数据字典。 这是否可能/可行,我们如何将其落实到位?

  • 使用临时数据库
    这个想法是使用一个单独的(不是内存中的)数据库,该数据库可以在应用程序的生命周期中创建。 打开“购物篮”后,我们可以将计算得出的数据持久保存到数据库中以供重复使用,从而减少了为同一“购物篮”进行重新计算的需求。

我们还有其他选择吗? 关于大数据计算并在RAM外部执行计算的最佳实践是什么?

任何建议表示赞赏。

作为那些绊脚石的更新...

我们最终使用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

}

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

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

它非常易于使用,您无需进行太多修改就可以将其放入代码中。 这是一个使用它的示例: https : //github.com/jprichardson/MemMapCache/blob/master/TestMemMapCache/MemMapCacheTest.cs

也许至少对您进一步了解实际解决方案需要做什么有用。

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

但是,从长期来看,我建议使用Redis。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM