C# OutOfMemory、映射内存文件或临时数据库
Posted
技术标签:
【中文标题】C# OutOfMemory、映射内存文件或临时数据库【英文标题】:C# OutOfMemory, Mapped Memory File or Temp Database 【发布时间】:2012-03-16 01:34:58 【问题描述】:寻求一些建议,最佳实践等......
技术:C# .NET4.0、Winforms、32 位
我正在就如何在我的 C# Winforms 应用程序中最好地处理大型数据处理寻求一些建议,该应用程序遇到高内存使用(工作集)和偶尔的 OutOfMemory 异常。
问题在于,当打开“购物篮”时,我们会在“内存中”执行大量数据处理。 简单来说,当加载“购物篮”时,我们会执行以下计算;
对于“购物篮”中的每件商品,检索其历史价格一直追溯到该商品首次出现在库存中的日期(可能是两个月、两年或二十年的数据)。历史价格数据是通过互联网从文本文件中检索的,价格插件支持的任何格式。
对于每件商品,自首次出现在库存以来的每一天,计算各种指标,从而为购物篮中的每件商品建立历史档案。
结果是我们可能会根据“购物篮”中的商品数量执行数百、数千和/或数百万次计算。如果篮子中的项目太多,我们可能会遇到“OutOfMemory”异常。
一些注意事项;
需要为“购物篮”中的每个项目计算此数据,并保留数据直到“购物篮”关闭。
即使我们在后台线程中执行第 1 步和第 2 步,速度也很重要,因为“购物篮”中的商品数量会极大地影响整体计算速度。
当“购物篮”关闭时,内存由 .NET 垃圾收集器回收。我们已经分析了我们的应用程序,并确保在关闭篮子时正确处理和关闭所有引用。
所有计算完成后,结果数据存储在 IDictionary 中。 "CalculatedData 是一个类对象,其属性是通过上述过程计算的各个指标。
我想到的一些想法;
显然,我主要关心的是减少计算使用的内存量,但是只有在我的情况下才能减少使用的内存量 1) 减少每天计算的指标数量或 2) 减少用于计算的天数。
如果我们希望满足我们的业务需求,这两个选项都不可行。
内存映射文件 一种想法是使用将存储数据字典的内存映射文件。这可能/可行吗?我们如何才能将其落实到位?
使用临时数据库 这个想法是使用一个单独的(不是内存中的)数据库,可以为应用程序的生命周期创建它。随着“购物篮”的打开,我们可以将计算出的数据持久化到数据库中以供重复使用,从而减轻了对同一个“购物篮”重新计算的需求。
还有其他我们应该考虑的替代方案吗?对大数据进行计算并在 RAM 之外执行这些计算的最佳做法是什么?
感谢任何建议....
【问题讨论】:
OutOfMemoryException
真的是 "out of address space" - 你考虑过迁移到 64 位吗?
【参考方案1】:
最简单的解决方案是数据库,也许是 SQLite。内存映射文件不会自动成为字典,您必须自己编写所有内存管理代码,从而与 .net GC 系统本身争夺数据的所有权。
【讨论】:
【参考方案2】:如果您有兴趣尝试内存映射文件方法,现在可以尝试。我编写了一个名为MemMapCache
的小型原生.NET 包,它实质上创建了一个由MemMappedFiles 支持的key/val 数据库。这是一个有点老套的概念,但 MemMapCache.exe 程序保留了对内存映射文件的所有引用,因此如果您的应用程序崩溃,您不必担心丢失缓存状态。
它使用起来非常简单,您应该可以将其放入您的代码中而无需进行太多修改。这是一个使用它的例子:https://github.com/jprichardson/MemMapCache/blob/master/TestMemMapCache/MemMapCacheTest.cs
也许它至少对您有一些用处,以至少进一步弄清楚您需要为实际解决方案做些什么。
如果您最终使用它,请告诉我。我会对你的结果感兴趣。
但是,从长远来看,我会推荐 Redis。
【讨论】:
谢谢JP。 Redis 是一个 NoSQL 数据库,为什么你更喜欢它?你认为它的优点和缺点是什么?不过感谢您的提示,我不知道并会调查。 Redis 非常快。它的命令集非常简单,一个人可以在 30 分钟内学会它。它是一个网络数据库,可以水平扩展。它不限于 1 MB 的对象。 你能回答我的问题吗?***.com/questions/9760073/…【参考方案3】:对于那些偶然发现这个线程的人的更新......
我们最终使用 SQLite 作为我们的缓存解决方案。我们使用的 SQLite 数据库与应用程序使用的主数据存储区分开存在。我们根据需要将计算的数据保存到 SQLite (diskCache),并使用代码控制缓存失效等。这对我们来说是一个合适的解决方案,因为我们能够实现写入速度和每秒大约 100,000 条记录。
对于那些感兴趣的人,这是控制插入到磁盘缓存中的代码。 这段代码的全部功劳归于 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
【讨论】:
以上是关于C# OutOfMemory、映射内存文件或临时数据库的主要内容,如果未能解决你的问题,请参考以下文章