寻求一些建议,最佳实践等......
技术:C#.NET4.0,Winforms,32位
我正在寻求一些关于如何在我的C#Winforms应用程序中最好地处理大数据处理的建议,该应用程序经历了高内存使用(工作集)和偶尔的OutOfMemory异常。
问题是当打开“购物篮”时,我们会在“内存中”执行大量数据处理。 以简单的术语加载“购物篮”时,我们执行以下计算;
对于“购物篮”中的每件商品,检索其历史价格一直回到商品首次出现在库存的日期(可能是两个月,两年或二十年的数据)。历史价格数据通过互联网从文本文件中检索,价格插件支持任何格式。
对于每件商品,自首次出现库存以来的每一天都会计算各种指标,这些指标会为购物篮中的每件商品构建历史档案。
结果是我们可以根据“购物篮”中的项目数量执行数百,数千和/或数百万次计算。如果购物篮包含太多商品,我们就有可能遇到“OutOfMemory”异常。
一些警告;
需要为“购物篮”中的每个项目计算此数据,并保留数据直至“购物篮”关闭。
即使我们在后台线程中执行步骤1和2,速度也很重要,因为“购物篮”中的项目数量会极大地影响整体计算速度。
当“购物篮”关闭时,.NET垃圾收集器会挽救内存。我们已经分析了我们的应用程序,并确保在关闭一个篮子时正确处理和关闭所有参考文件。
完成所有计算后,结果数据存储在IDictionary中。 “CalculatedData是一个类对象,其属性是由上述过程计算的单个度量。
我想过的一些想法;
显然我主要关心的是减少计算所使用的内存量,但是如果我用的话,只能减少使用的内存量
1)减少每天计算的指标数量或
2)减少用于计算的天数。
如果我们希望满足业务需求,这两种选择都是不可行的。
内存映射文件
一个想法是使用将存储数据字典的内存映射文件。这是可行/可行的,我们如何才能实现这一目标?
使用临时数据库
我们的想法是使用一个单独的(非内存中)数据库,该数据库可以为应用程序的生命周期创建。随着“购物篮”被打开,我们可以将计算出的数据保存到数据库中以供重复使用,从而减少重新计算同一“购物篮”的要求。
我们应该考虑其他替代方案吗?在计算大数据并在RAM之外执行它们时,最佳做法是什么?
任何建议都表示赞赏....
答案 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
}