2012-02-26 70 views
2

尋求一些建議,最佳實踐等..C#內存溢出,內存映射文件或臨時數據庫

技術:C#.NET4.0,的WinForms,32位

我尋求如何我一些建議可以最好地處理我的C#Winforms應用程序中的大量數據處理,這些應用程序經歷了高內存使用(工作集)和偶爾的OutOfMemory異常。

問題是,當「購物籃」打開時,我們會執行大量的「內存中」數據處理。 簡單來說,當「購物籃」加載時,我們執行以下計算;

  1. 對於「購物籃」的每個項目進行檢索歷史價格將所有的方式回到項目首次出現在股票的日期(可能是兩個月的時間,兩年二十年的數據)。歷史價格數據通過互聯網從文本文件中檢索,價格插件支持的任何格式。

  2. 對於每件商品,自首次出現在庫存中以來的每一天計算各種指標,爲購物籃中的每件商品建立歷史配置文件。

其結果是,我們可以執行數百,千和/或數百萬計算,具體取決於「購物籃」中的物品數量。如果籃子包含太多項目,我們會冒着發生「OutOfMemory」異常的風險。

一些注意事項;

  1. 需要爲「購物籃」中的每個商品計算這些數據,數據一直保存到「購物籃」關閉。

  2. 儘管我們在後臺線程中執行步驟1和2,但速度非常重要,因爲「購物籃」中的項目數量可以極大地影響整體計算速度。

  3. 當「購物籃」關閉時,內存被.NET垃圾回收器搶救。我們對我們的應用程序進行了簡要介紹,並確保在關閉購物籃時所有參考資料都能正確處理和關閉。

  4. 完成所有計算後,結果數據存儲在IDictionary中。 「CalculatedData是一類對象,其屬性是通過上述方法計算出各個指標

一些想法,我也想過。

顯然,我的主要關注點是降低的內存量是使用但只能減少所使用的存儲器的量的計算,如果我
1)降低度量的數目來計算的每一天或
2)減少用於計算的天數。

如果我們希望實現我們的業務需求,這兩個選項都不可行。

  • 內存映射文件
    一個想法是使用內存映射將存儲數據字典文件。這是否可行/可行?我們如何才能實現這一目標?

  • 使用臨時數據庫
    這個想法是使用一個單獨的(不是內存中的)數據庫,它可以爲應用程序的生命週期創建。隨着「購物籃」的開放,我們可以將計算的數據保存到數據庫中以便重複使用,從而減輕重新計算相同「購物籃」的要求。

我們應該考慮其他的替代方案嗎?在計算大數據並在RAM之外進行計算時,最佳做法是什麼?

任何意見是讚賞....

+0

'OutOfMemoryException'真的[ 「出的地址空間」](http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of -memory-does-not-refer-to-physical-memory.aspx) - 你有沒有考慮遷移到64位? – 2012-02-26 04:40:01

回答

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 

}

0

最簡單的解決方案是數據庫,也許SQLite。內存映射文件不會自動成爲字典,您必須自己編寫所有內存管理,從而與.net GC系統本身打交道,以獲取數據的所有權。

0

如果您有興趣嘗試使用內存映射文件方法,可以現在就試用它。我編寫了一個名爲MemMapCache的小型本地.NET包,實質上它創建了一個由MemMappedFiles支持的key/val數據庫。這是一個有點冒險的概念,但MemMapCache.exe程序會保留所有對內存映射文件的引用,這樣如果你的應用程序崩潰了,你不必擔心丟失緩存的狀態。

使用起來非常簡單,您應該可以將其放在代碼中,而無需太多修改。這裏是一個使用它的例子:https://github.com/jprichardson/MemMapCache/blob/master/TestMemMapCache/MemMapCacheTest.cs

也許這對你至少需要進一步弄清楚你需要爲實際的解決方案做些什麼。

請讓我知道,如果你最終使用它。我會對你的結果感興趣。

但是,長期來看,我推薦Redis。

+0

謝謝JP。 Redis是一個NoSQL數據庫,你爲什麼更喜歡別人?你認爲這有什麼優點和缺點? 感謝您的提示,但我沒有意識到這一點,並會進行調查。 – paligap 2012-02-26 07:40:03

+0

Redis非常快速。它的命令集非常簡單,一個人可以在30分鐘內完成。這是一個網絡數據庫,可以水平擴展。它不限於1 MB的對象。 – 2012-02-27 20:14:16

+0

可以請你回答我的問題嗎?http://stackoverflow.com/questions/9760073/memorymappedfile-doesnt-work-with-2-processes – 2012-03-18 16:42:58