. net内存管理-提交增长导致OOM
本文关键字:OOM 提交 内存 管理 net | 更新日期: 2023-09-27 18:17:41
我对c#比较陌生,并且对我得到的OOM错误完全感到困惑。我试图创建一个稀疏矩阵,因此收集三元组(行索引,列索引,值)。在进行for循环时,最终发生的情况是进程使用的实际物理内存(我相信,根据资源管理器,Windows称之为"工作集")相对固定在3.5GB左右。但是,提交(我认为是虚拟内存)会不断增加,直到达到提交限制,此时我的程序会因OOM错误而崩溃。
相关代码如下:
public SimMatrix(string sparseMethod, string simMethod, Phrases phrases, DistrFeature features, int topK) {
List<int> rows = new List<int>(phrases.uniquePhraseCount*topK);
List<int> cols = new List<int>(phrases.uniquePhraseCount*topK);
List<double> vals = new List<double>(phrases.uniquePhraseCount*topK);
if (sparseMethod.Equals("invIdx")) {
List<int> nonzeros = new List<int>(features.inverted_idx.Count());
List<int> neighbors = new List<int>(phrases.uniquePhraseCount);
List<double> simVals = new List<double>(phrases.uniquePhraseCount);
List<int> sortedIdx = new List<int>(phrases.uniquePhraseCount);
List<double> sortedSim = new List<double>(phrases.uniquePhraseCount);
for (int i = 0; i < phrases.uniquePhraseCount; i++) { //loop through all phrases
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
{
if (phrases.feature_values.RowLength(i) > 0) { //i.e., at least one feature fired for phrase
nonzeros = (from pmi in row.Elements select pmi.IndexList[1]).ToList();
neighbors = generateNeighbors(nonzeros, features.inverted_idx);
foreach (int neighbor in neighbors)
simVals.Add(cosineSimilarity(row, phrases.feature_values.GetRowSparse(neighbor)));
var sortedIdxSim = neighbors.Zip(simVals, (a, b) => new { idx = a, sim = b }).OrderByDescending(pair => pair.sim);
sortedIdx = sortedIdxSim.Select(pair => pair.idx).ToList();
sortedSim = sortedIdxSim.Select(pair => pair.sim).ToList();
int topN = (sortedIdxSim.Count() < topK) ? sortedIdxSim.Count() : topK;
rows.AddRange(Enumerable.Repeat(i, topN).ToList());
cols.AddRange(sortedIdx.Take(topN).ToList());
vals.AddRange(sortedSim.Take(topN).ToList());
nonzeros.Clear();
neighbors.Clear();
simVals.Clear();
sortedIdx.Clear();
sortedSim.Clear();
}
else { //just add self similarity
rows.Add(i);
cols.Add(i);
vals.Add(1);
}
Console.WriteLine("{0} phrases done", i + 1);
}
}
}
else { Console.WriteLine("Sorry, no other sparsification method implemented thus far"); }
simMat = new SparseDoubleArray(phrases.uniquePhraseCount, phrases.uniquePhraseCount, rows, cols, vals);
}
static private List<int> generateNeighbors(List<int> idx, Dictionary<int, List<int>> inverted_idx) {
List<int> neighbors = new List<int>();
foreach (int feature in idx) {
neighbors.AddRange(inverted_idx[feature]);
neighbors = neighbors.Distinct().ToList();
}
return neighbors;
}
static public double cosineSimilarity(SparseDoubleArray profile1, SparseDoubleArray profile2) {
double numerator = profile1.Dot(profile2);
double norm1 = profile1.Norm();
double norm2 = profile2.Norm();
double cos_sim = numerator / (norm1 * norm2);
if (cos_sim > 0)
return cos_sim;
else
return 0;
}
注意代码使用了一些内部库(例如,SparseDoubleArray对象)。基本要点是,我循环遍历所有条目(以I为索引),并为每个条目找出非零列索引,然后通过"generatenneighbors"函数从中生成潜在邻居列表。一旦我有了候选邻居的列表,我计算每个潜在邻居的余弦相似度。然后,我同时对索引和相似度值进行排序,选择topN索引/相似度值,并将它们与索引I(对应于行索引)一起添加到维护稀疏矩阵索引和值的列表中。
代码在执行for循环时似乎不确定地中断。有时它在i = 25000时断裂,有时在i = 2000时断裂。我甚至没有到达初始化稀疏矩阵的阶段。
任何见解或帮助将不胜感激。
更新(2013年6月10日)
由于提供的响应,我已经设法大大减少了代码的提交内存。下面是更新后的代码,您会注意到它与问题的回答不完全相同,我会详细说明我需要更改的地方。
public SimMatrix(string sparseMethod, string simMethod, Phrases phrases, DistrFeature features, int topK) {
List<int> rows = new List<int>(phrases.uniquePhraseCount*topK);
List<int> cols = new List<int>(phrases.uniquePhraseCount*topK);
List<double> vals = new List<double>(phrases.uniquePhraseCount*topK);
if (sparseMethod.Equals("invIdx")) {
for (int i = 0; i < phrases.uniquePhraseCount; i++) { //loop through all phrases
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
{
if (phrases.feature_values.RowLength(i) > 0) { //i.e., at least one feature fired for phrase
IEnumerable<int> nonzeros = from pmi in row.Elements select pmi.IndexList[1];
IEnumerable<int> neighbors = nonzeros.SelectMany(x => features.inverted_idx[x]).Distinct();
IEnumerable<double> simVals = neighbors.Select(x => cosineSimilarity(row, x, phrases));
var sortedIdxSim = neighbors.Zip(simVals, (a, b) => new { idx = a, sim = b }).OrderByDescending(pair => pair.sim).ToList();
//IEnumerable<int> sortedIdx = sortedIdxSim.Select(pair => pair.idx);
//IEnumerable<double> sortedSim = sortedIdxSim.Select(pair => pair.sim);
int sortedIdxSimCount = sortedIdxSim.Count;
int topN = (sortedIdxSimCount < topK) ? sortedIdxSimCount : topK;
rows.AddRange(Enumerable.Repeat(i, topN));
cols.AddRange(sortedIdxSim.Take(topN).Select(pair => pair.idx));
vals.AddRange(sortedIdxSim.Take(topN).Select(pair => pair.sim));
}
else { //just add self similarity
rows.Add(i);
cols.Add(i);
vals.Add(1);
}
if ((i % 1000) == 0)
Console.WriteLine("{0} phrases done;", i + 1);
}
}
}
else { Console.WriteLine("Sorry, no other sparsification method implemented thus far"); }
simMat = new SparseDoubleArray(phrases.uniquePhraseCount, phrases.uniquePhraseCount, rows, cols, vals);
}
static public double cosineSimilarity(SparseDoubleArray profile1, int profile2idx, Phrases phrases) {
using (SparseDoubleArray profile2 = phrases.feature_values.GetRowSparse(profile2idx)) {
double numerator = profile1.Dot(profile2);
double norm1 = profile1.Norm();
double norm2 = profile2.Norm();
double cos_sim = numerator / (norm1 * norm2);
if (cos_sim > 0)
return cos_sim;
else
return 0;
}
}
首先,我被迫将var sortedIdxSim
从IEnumerable转换为List;这是因为我a)需要知道这个列表中的元素数量,并且似乎在IEnumerable上调用.Count()
会删除IEnumerable中保存的数据?似乎在IEnumerable<int> sortedIdx
上调用.Take()
(例如,根据Gjeltema的原始建议)也会删除IEnumerable<double> sortedSim
中的数据。这是因为延迟执行吗?我不太熟悉延迟求值/延迟执行,所以我可能误解了这里需要做的事情。
然而,老实说,目前的变化已经大大减少了我的提交内存,这样的程序可以实际运行到完成,所以非常感谢!如果有人能帮我澄清上面的问题,那就太好了。
一个问题是,您过早地声明了一堆临时集合,并将它们初始化为看起来远远超出它们实际需要的大小。然后通过给它们赋其他值来丢弃分配给它们的内存。这可能不是您的主要问题,因为您几乎立即丢弃了初始化的集合(将其释放出来用于垃圾收集),但我确信这没有帮助。
例如,初始化neighbors
如下:
List<int> neighbors = new List<int>(phrases.uniquePhraseCount);
然后在第一次使用neighbors
时,为它分配一个新的集合,丢弃为它分配的内存:
neighbors = generateNeighbors(nonzeros, features.inverted_idx);
所以,第一件事是你会想要摆脱所有那些你没有使用的早期初始化,它们可能会占用相当大的内存块。
下一件事是你使用Linq语句很多,大概是为了可读性和容易获得你想要的数据,这是伟大的。但是,你没有利用Linq的一个特性(特别是在低内存的情况下),在所有的事情上调用.ToList()
,而不是延迟加载。
我已经检查了你的函数并删除了我上面提到的初始化,并且还尽可能多地更改为延迟加载(即删除.ToList()
调用)。
(请注意,我将.ToList()
调用留给neighbors
初始化,因为我认为不这样做不会获得太多好处(很难从这段代码中判断neighbors
将有多大)。如果您仍然有内存问题,我建议将generateNeighbors()
的返回类型更改为IEnumerable
,并删除其中的.ToList()
并尝试)。
这将大大降低您的峰值内存使用。如果你仍然有内存问题,回来更新你的问题——我可能需要看到更多的代码,并获得更多关于你在那一点上运行的数字类型的信息。
// Side note - your simMethod argument doesn't seem to be used.
public SimMatrix(string sparseMethod, string simMethod, Phrases phrases, DistrFeature features, int topK)
{
List<int> rows = new List<int>(phrases.uniquePhraseCount * topK);
List<int> cols = new List<int>(phrases.uniquePhraseCount * topK);
List<double> vals = new List<double>(phrases.uniquePhraseCount * topK);
if (sparseMethod.Equals("invIdx"))
{
for (int i = 0; i < phrases.uniquePhraseCount; i++)
{ //loop through all phrases
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
{
if (phrases.feature_values.RowLength(i) > 0)
{ //i.e., at least one feature fired for phrase
// Declare your temporary collections when they're initialized
IEnumerable<int> nonzeros = row.Elements.Select(pmi => pmi.IndexList[1]);
var neighbors = generateNeighbors(nonzeros, features.inverted_idx);
IEnumerable<double> simVals = neighbors.Select(x => cosineSimilarity(row, phrases.feature_values.GetRowSparse(x)));
var sortedIdxSim = neighbors.Zip(simVals, (a, b) => new { idx = a, sim = b }).OrderByDescending(pair => pair.sim);
IEnumerable<int> sortedIdx = sortedIdxSim.Select(pair => pair.idx);
IEnumerable<double> sortedSim = sortedIdxSim.Select(pair => pair.sim);
int sortedInxSimCount = sortedIdxSim.Count();
int topN = (sortedInxSimCount < topK) ? sortedInxSimCount : topK;
rows.AddRange(Enumerable.Repeat(i, topN));
cols.AddRange(sortedIdx.Take(topN));
vals.AddRange(sortedSim.Take(topN));
}
else
{ //just add self similarity
rows.Add(i);
cols.Add(i);
vals.Add(1);
}
Console.WriteLine("{0} phrases done", i + 1);
}
}
}
else { Console.WriteLine("Sorry, no other sparsification method implemented thus far"); }
simMat = new SparseDoubleArray(phrases.uniquePhraseCount, phrases.uniquePhraseCount, rows, cols, vals);
}
static private List<int> generateNeighbors(IEnumerable<int> idx, Dictionary<int, List<int>> inverted_idx)
{
// Doing it this way will reduce memory usage since you won't be creating a bunch of temporary
// collections, adding them to an existing collection, then creating a brand new collection
// from it that is smaller... I think that may have been spiking your memory usage quite a bit.
return inverted_idx.Where(x => idx.Contains(x.Key)).SelectMany(x => x.Value).Distinct().ToList();
}
最后一个注意-你似乎添加了什么看起来是相同的值至少rows
,可能cols
和vals
。您是否需要这些集合中的重复值(看起来实际上可能需要)?如果它们从来没有超过它们的初始化容量(phrases.uniquePhraseCount * topK
),这不是一个真正的问题,但如果它们超过了,那么这可能是您的主要内存问题。
SparseDoubleArray
类,GetRowSparse()
做什么?我特别想知道为什么你在课堂上做using
,像这样:
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
强制它在块完成后释放其本地资源。但是,你也在这里调用它:
simVals.Add(cosineSimilarity(row, phrases.feature_values.GetRowSparse(neighbor)));
但不调用Dispose()
。在这个函数和这个类中发生了什么?是不需要using
,还是实际上需要它?如果需要,那么这可能是内存泄漏。