【问题标题】:C# Dictionary and Efficient Memory UsageC# 字典和高效的内存使用
【发布时间】:2010-01-29 20:14:00
【问题描述】:

我有一个工具来比较 2 个 csv 文件,然后将每个单元格存储到 6 个存储桶中的一个中。基本上,它读取 csv 文件(使用快速 csv 阅读器,信用:http://www.codeproject.com/KB/database/CsvReader.aspx),然后根据用户提供的密钥创建与每个文件相关的字典。然后我遍历字典比较值并编写结果 csv 文件。

虽然速度非常快,但在内存使用方面却非常低效。我无法将机器上超过 150 MB 的文件与 3 GB 物理内存进行比较。

这是读取预期文件的代码 sn-p。在这篇文章的最后,任务管理器的内存使用量接近 500 MB。

// Read Expected
long rowNumExp;
System.IO.StreamReader readerStreamExp = new System.IO.StreamReader(@expFile);
SortedDictionary<string, string[]> dictExp = new SortedDictionary<string, string[]>();
List<string[]> listDupExp = new List<string[]>();
using (CsvReader readerCSVExp = new CsvReader(readerStreamExp, hasHeaders, 4096))
{
    readerCSVExp.SkipEmptyLines = false;
    readerCSVExp.DefaultParseErrorAction = ParseErrorAction.ThrowException;
    readerCSVExp.MissingFieldAction = MissingFieldAction.ParseError;
    fieldCountExp = readerCSVExp.FieldCount;                
    string keyExp;
    string[] rowExp = null;
    while (readerCSVExp.ReadNextRecord())
    {
        if (hasHeaders == true)
        {
            rowNumExp = readerCSVExp.CurrentRecordIndex + 2;
        }
        else
        {
            rowNumExp = readerCSVExp.CurrentRecordIndex + 1;
        }
        try
        {
            rowExp = new string[fieldCount + 1];                    
        }
        catch (Exception exExpOutOfMemory)
        {
            MessageBox.Show(exExpOutOfMemory.Message);
            Environment.Exit(1);
        }                
        keyExp = readerCSVExp[keyColumns[0] - 1];
        for (int i = 1; i < keyColumns.Length; i++)
        {
            keyExp = keyExp + "|" + readerCSVExp[i - 1];
        }
        try
        {
            readerCSVExp.CopyCurrentRecordTo(rowExp);
        }
        catch (Exception exExpCSVOutOfMemory)
        {
            MessageBox.Show(exExpCSVOutOfMemory.Message);
            Environment.Exit(1);
        }
        try
        {
            rowExp[fieldCount] = rowNumExp.ToString();
        }
        catch (Exception exExpRowNumOutOfMemory)
        {
            MessageBox.Show(exExpRowNumOutOfMemory.Message);
            Environment.Exit(1);
        }
        // Dedup Expected                        
        if (!(dictExp.ContainsKey(keyExp)))
        {
            dictExp.Add(keyExp, rowExp);                        
        }
        else
        {
            listDupExp.Add(rowExp);
        }                    
    }                
    logFile.WriteLine("Done Reading Expected File at " + DateTime.Now);
    Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
    logFile.WriteLine("Done Creating Expected Dictionary at " + DateTime.Now);
    logFile.WriteLine("Done Identifying Expected Duplicates at " + DateTime.Now + "\r\n");                
}

有什么办法可以提高内存效率吗?我可以在上面做些什么来减少内存消耗?

欢迎提出任何想法。

感谢大家的反馈。

我已按照建议合并更改以将行的索引而不是行本身存储在字典中。

这是与新实现相同的代码片段。

// Read Expected
        long rowNumExp;
        SortedDictionary<string, long> dictExp = new SortedDictionary<string, long>();
        System.Text.StringBuilder keyExp = new System.Text.StringBuilder();
        while (readerCSVExp.ReadNextRecord())
        {
            if (hasHeaders == true)
            {
                rowNumExp = readerCSVExp.CurrentRecordIndex + 2;
            }
            else
            {
                rowNumExp = readerCSVExp.CurrentRecordIndex + 1;
            }
            for (int i = 0; i < keyColumns.Length - 1; i++)
            {
                keyExp.Append(readerCSVExp[keyColumns[i] - 1]);
                keyExp.Append("|");
            }
            keyExp.Append(readerCSVExp[keyColumns[keyColumns.Length - 1] - 1]);
            // Dedup Expected                       
            if (!(dictExp.ContainsKey(keyExp.ToString())))
            {
                dictExp.Add(keyExp.ToString(), rowNumExp);
            }
            else
            {
                // Process Expected Duplicates          
                string dupExp;
                for (int i = 0; i < fieldCount; i++)
                {
                    if (i >= fieldCountExp)
                    {
                        dupExp = null;
                    }
                    else
                    {
                        dupExp = readerCSVExp[i];
                    }
                    foreach (int keyColumn in keyColumns)
                    {
                        if (i == keyColumn - 1)
                        {
                            resultCell = "duplicateEXP: '" + dupExp + "'";
                            resultCell = CreateCSVField(resultCell);
                            resultsFile.Write(resultCell);
                            comSumCol = comSumCol + 1;
                            countDuplicateExp = countDuplicateExp + 1;
                        }
                        else
                        {
                            if (checkPTColumns(i + 1, passthroughColumns) == false)
                            {
                                resultCell = "'" + dupExp + "'";
                                resultCell = CreateCSVField(resultCell);
                                resultsFile.Write(resultCell);
                                countDuplicateExp = countDuplicateExp + 1;
                            }
                            else
                            {
                                resultCell = "PASSTHROUGH duplicateEXP: '" + dupExp + "'";
                                resultCell = CreateCSVField(resultCell);
                                resultsFile.Write(resultCell);
                            }
                            comSumCol = comSumCol + 1;
                        }
                    }
                    if (comSumCol <= fieldCount)
                    {
                        resultsFile.Write(csComma);
                    }
                }
                if (comSumCol == fieldCount + 1)
                {
                    resultsFile.Write(csComma + rowNumExp);
                    comSumCol = comSumCol + 1;
                }
                if (comSumCol == fieldCount + 2)
                {
                    resultsFile.Write(csComma);
                    comSumCol = comSumCol + 1;
                }
                if (comSumCol > fieldCount + 2)
                {
                    comSumRow = comSumRow + 1;
                    resultsFile.Write(csCrLf);
                    comSumCol = 1;
                }
            }
            keyExp.Clear();
        }
        logFile.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
        Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
        logFile.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n");
        Console.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n");
        logFile.Flush();

但是,问题是我需要内存中的两个数据集。实际上,我会遍历这两个字典,根据键查找匹配、不匹配、重复和丢失。

使用这种存储行索引的方法,我仍然使用大量内存,因为对于动态访问,我现在必须使用缓存版本的 csv 阅读器。因此,尽管现在字典要小得多,但数据缓存弥补了节省,我最终还是得到了差不多的内存使用量。

希望,我说得有道理...:)

一种选择是完全摆脱字典,只循环浏览 2 个文件,但不确定性能是否会像比较 2 个字典一样快。

非常感谢任何输入。

【问题讨论】:

  • 而不是缓存csv阅读器,就不能缓存文件中的记录位置,这样以后可以取回记录吗?当您遍历字典以查找丢失等按键时,您是在查看实际数据还是仅查看键?
  • 您是否尝试在字符串进入字典之前对其进行实习?有什么不同吗?这些对内存使用有帮助吗?

标签: c# dictionary memory-management


【解决方案1】:

您可以用 StringBuilder 替换keyExp。在这样的循环中重新分配字符串将继续分配更多内存,因为字符串是不可变的。

StringBuilder keyExp = new StringBuilder();
...
    keyExp.Append("|" + readerCSVExp[i - 1]) ;
... 

很多字符串都一样吗?你可以试试interning them,那么任何相同的字符串都将共享相同的内存而不是副本...

rowExp[fieldCount] = String.Intern(rowNumExp.ToString()); 

// Dedup Expected               
string internedKey = (String.Intern(keyExp.ToString()));        
if (!(dictExp.ContainsKey(internedKey)))
{
   dictExp.Add(internedKey, rowExp);                        
}
else
{
   listDupExp.Add(rowExp);
}  

我不确定代码到底是如何工作的,但是...除此之外,我会说您不需要在字典中保留 rowExp,保留其他内容,例如数字并写回 rowExp输出到另一个文件中的磁盘。这可能会为您节省最多的内存,因为这似乎是文件中的字符串数组,因此可能很大。如果您将其写入文件并将该数字保留在文件中,那么您可以在将来需要处理时再次返回它。如果您将文件中的偏移量保存为字典中的值,则可以快速再次找到它。也许:)。

【讨论】:

  • 有趣,我在想编译器/解释器/抖动/某些东西会自动插入字符串,但这可能仅适用于我猜在编译时已知相同的字符串。
  • @Davy8,这是正确的。默认情况下,字符串实习仅发生在从编译时常量创建的字符串上。
【解决方案2】:

如果我有什么问题,请告诉我。

上面的代码读取一个 CSV 文件并查找重复的键。每一行进入两个集合中的一个,一个用于重复键,一个没有。

您如何处理这些行集?

它们是否写入不同的文件?

如果是这样,则没有理由将非唯一行存储在列表中,因为您会发现它们将它们写入文件。

当您确实找到重复项时,无需存储整行,只需存储键,然后将行写入文件(如果您想将它们分开,显然是不同的文件)。

如果您需要对不同的集合进行进一步处理,那么当不存储行号时,而不是存储整行。然后,当您对行执行任何操作时,您就有了再次获取行所必需的行号。

注意:您可以将偏移量存储在行起点的文件中,而不是存储行号。然后,如果需要,您可以访问文件并随机读取行。

只要对你可能有的任何问题(或澄清)评论这个答案,我会更新答案,无论如何我会在这里再待几个小时。

编辑
您可以通过不存储密钥,而是存储密钥的哈希值来进一步减少内存占用。如果找到重复项,请在文件中查找该位置,重新读取该行并比较实际键。

【讨论】:

  • 请看我在上面编辑过的帖子中的回复。抱歉,不知道如何将代码示例成功粘贴到 cmets 中。
【解决方案3】:

如果您还没有像 DotTrace 这样的分析器来查看哪些对象正在使用内存,那么您可以很好地了解需要优化的内容。

看代码的一些想法:

你需要存储listDupExp吗?在我看来,您正在有效地将两个文件加载到内存中,因此 2 x 150MB + 一些开销在任务管理器中很容易接近 500MB。

其次,你能在读完所有输入之前就开始写输出吗?我认为这很棘手,因为看起来您需要在写出所有输出项之前对其进行排序,但可能是您可以查看的内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 2017-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多