簡體   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