1

我对 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 索引),并为每个条目找出非零列索引,然后通过“generateNeighbors”函数从中生成潜在邻居列表。一旦有了候选邻居列表,我就会计算每个潜在邻居的余弦相似度。然后,我同时对索引和相似度值进行排序,选择 topN 索引/相似度值,并将它们与索引 i(对应于行索引)一起添加到维护稀疏矩阵索引和值的列表中。

代码在执行 for 循环时看似不确定地中断。有时它在 i = 25,000 时中断,有时在 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 sortedIdxSimIEnumerable 转换为 List;这是因为我 a) 需要知道此列表中的元素数量,并且似乎调用.Count()IEnumerable 会清除 IEnumerable 中保存的数据?似乎调用(例如,.Take()根据IEnumerable<int> sortedIdxGjeltema 的原始建议)会清除IEnumerable<double> sortedSim. 这是因为延期执行吗?我对延迟评估/延迟执行不太熟悉,所以也许我误解了我需要在这里做什么。

然而,老实说,这里的当前更改大大减少了我的提交内存,因此程序实际上可以运行到完成,非常感谢!如果有人可以帮助我澄清上述问题,那就太好了。

4

1 回答 1

2

一个问题是你提前声明了一堆临时集合,并用看起来远远超出他们实际需要的大小来初始化它们。然后,您继续通过为它们分配其他值来丢弃分配给它们的内存。这可能不是您的主要问题,因为您几乎立即丢弃了初始化的集合(将其释放以进行垃圾收集),但我确信它没有帮助。

例如,您neighbors像这样初始化:

List<int> neighbors = new List<int>(phrases.uniquePhraseCount);

然后在第一次使用 时neighbors,为它分配一个新集合,丢弃为它分配的内存:

neighbors = generateNeighbors(nonzeros, features.inverted_idx);

所以,第一件事是你会想要摆脱所有那些你不使用的早期初始化,它们可能会占用相当大的内存。

接下来是你大量使用 Linq 语句,大概是为了可读性和轻松获取你想要的数据,这很棒。但是,您不会通过调用.ToList()所有内容而不是延迟加载所有内容来利用 Linq 的功能之一(尤其是在内存不足的情况下)。

我已经检查了你的函数并删除了我上面提到的初始化,并且还尽可能多地更改为延迟加载(即删除.ToList()调用)。

(请注意,我离开了初始化.ToList()调用,neighbors因为我认为不这样做不会获得太多收益(很难判断neighbors这段代码会有多大)。如果你仍然有内存问题,我建议改变generateNeighbors()to的返回类型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,并且可能colsvals。您是否需要这些集合中的重复值(看起来您实际上可能需要)?如果它们从未超过其初始化容量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不需要,还是真的需要?如果需要,那可能是您的内存泄漏。

于 2013-06-08T19:54:41.843 回答