我需要处理一个非常大的文本文件(6-8 GB)。我写了下面的代码。不幸的是,每次输出文件达到(在源文件旁边创建)达到〜2GB时,我发现内存消耗突然增加(从100MB到几GB),结果-内存不足。
调试器指示OOM发生在while ((tempLine = streamReader.ReadLine()) != null)
我仅针对.NET 4.7和x64体系结构。
单行最长为50个字符。
我可以解决此问题,并将原始文件拆分为较小的部分,以免在处理时遇到问题,最后将结果合并到一个文件中,但不想这样做。
代码:
public async Task PerformDecodeAsync(string sourcePath, string targetPath)
{
var allLines = CountLines(sourcePath);
long processedlines = default;
using (File.Create(targetPath));
var streamWriter = File.AppendText(targetPath);
var decoderBlockingCollection = new BlockingCollection<string>(1000);
var writerBlockingCollection = new BlockingCollection<string>(1000);
var producer = Task.Factory.StartNew(() =>
{
using (var streamReader = new StreamReader(File.OpenRead(sourcePath), Encoding.Default, true))
{
string tempLine;
while ((tempLine = streamReader.ReadLine()) != null)
{
decoderBlockingCollection.Add(tempLine);
}
decoderBlockingCollection.CompleteAdding();
}
});
var consumer1 = Task.Factory.StartNew(() =>
{
foreach (var line in decoderBlockingCollection.GetConsumingEnumerable())
{
short decodeCounter = 0;
StringBuilder builder = new StringBuilder();
foreach (var singleChar in line)
{
var positionInDecodeKey = decodingKeysList[decodeCounter].IndexOf(singleChar);
if (positionInDecodeKey > 0)
builder.Append(model.Substring(positionInDecodeKey, 1));
else
builder.Append(singleChar);
if (decodeCounter > 18)
decodeCounter = 0;
else ++decodeCounter;
}
writerBlockingCollection.TryAdd(builder.ToString());
Interlocked.Increment(ref processedlines);
if (processedlines == (long)allLines)
writerBlockingCollection.CompleteAdding();
}
});
var writer = Task.Factory.StartNew(() =>
{
foreach (var line in writerBlockingCollection.GetConsumingEnumerable())
{
streamWriter.WriteLine(line);
}
});
Task.WaitAll(producer, consumer1, writer);
}
非常感谢您提供解决方案以及如何对其进行优化的建议。
答案 0 :(得分:0)
由于您所做的工作主要是与IO绑定,因此您实际上并没有从并行化中获得任何收益。在我看来,(如果我错了,请纠正我)您的转换算法不取决于您逐行读取文件,因此我建议改为执行以下操作:
void Main()
{
//Setup streams for testing
using(var inputStream = new MemoryStream())
using(var outputStream = new MemoryStream())
using (var inputWriter = new StreamWriter(inputStream))
using (var outputReader = new StreamReader(outputStream))
{
//Write test string and rewind stream
inputWriter.Write("abcdefghijklmnop");
inputWriter.Flush();
inputStream.Seek(0, SeekOrigin.Begin);
var inputBuffer = new byte[5];
var outputBuffer = new byte[5];
int inputLength;
while ((inputLength = inputStream.Read(inputBuffer, 0, inputBuffer.Length)) > 0)
{
for (var i = 0; i < inputLength; i++)
{
//transform each character
outputBuffer[i] = ++inputBuffer[i];
}
//Write to output
outputStream.Write(outputBuffer, 0, inputLength);
}
//Read for testing
outputStream.Seek(0, SeekOrigin.Begin);
var output = outputReader.ReadToEnd();
Console.WriteLine(output);
//Outputs: "bcdefghijklmnopq"
}
}
很明显,您将使用FileStreams而不是MemoryStreams,并且可以将缓冲区长度增加到更大的长度(因为这只是一个示例)。同样,由于您的原始方法是Async,因此您将使用Stream.Write和Stream.Read
的异步变体。答案 1 :(得分:0)
就像我说的那样,我可能会先做一些简单的事情,除非或直到证明它运行不佳为止。正如Adi在回答中所说,这项工作似乎受I / O约束-因此为它创建多个任务似乎没有什么好处。
publiv void PerformDecode(string sourcePath, string targetPath)
{
File.WriteAllLines(targetPath,File.ReadLines(sourcePath).Select(line=>{
short decodeCounter = 0;
StringBuilder builder = new StringBuilder();
foreach (var singleChar in line)
{
var positionInDecodeKey = decodingKeysList[decodeCounter].IndexOf(singleChar);
if (positionInDecodeKey > 0)
builder.Append(model.Substring(positionInDecodeKey, 1));
else
builder.Append(singleChar);
if (decodeCounter > 18)
decodeCounter = 0;
else ++decodeCounter;
}
return builder.ToString();
}));
}
现在,当然,这段代码实际上会阻塞直到完成,这就是为什么我没有将其标记为async
的原因。但是,您的也是如此,它应该已经对此发出了警告。
(您可以尝试在Select
部分使用PLINQ而不是LINQ,但老实说,我们在这里执行的处理的数量看起来微不足道;在应用任何此类更改之前,首先进行分析)