我正在尝试用随机数据填充超大文件(> 1GB)。
我已经编写了简单的“线程安全随机”函数,它可以生成字符串(在https://devblogs.microsoft.com/pfxteam/getting-random-numbers-in-a-thread-safe-way/中提出了解决方案),并且对随机数进行重做以使随机字符串变得微不足道。
我正在尝试使用以下代码将其写入文件:
String rp;
Parallel.For(1, numlines -1, i =>
{
rp = ThreadSafeRandom.Next();
outputFile.WriteLineAsync(rp.ToString()).Wait();
});
当行号较小时,会完美地生成文件。
当我输入更多的行数(例如30000)时,会发生以下情况:
某些字符串已损坏(Notepad ++认为它们被许多NUL开头)
有时我会收到InvalidOperationException(“线程由先前的线程操作使用”)。
我尝试用Parallel.For(1, numlines -1, async i =>
制作await outputFile.WriteLineAsync(rp.ToString());
并且尝试做
lock (outputFile) {
outputFile.WriteLineAsync(rp.ToString());
}
我总是可以将单线程方法与简单的for和simple writeLine()一起使用,但是正如我已经说过的,我想生成大文件,并且我假设即使是简单的for循环,它生成> 10000条记录也要花费一些时间(在文件中)如果存储量很大,我们将拥有1e + 6甚至1e9记录,即大于20GB),我想不出任何最佳方法。
有人可以建议如何优化它吗?
答案 0 :(得分:2)
您的限制因素可能是硬盘速度。不过,将工作一分为二可能会获得一些性能。一个线程(生产者)将产生随机行,而另一个线程(消费者)将所产生的行写入文件中。下面的代码在不到一秒钟(10 MB)的时间内将1,000,000条随机行写入了我的SSD中的文件。
var buffer = new BlockingCollection<string>(boundedCapacity: 10);
var producer = Task.Factory.StartNew(() =>
{
var random = new Random();
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++) // 10,000 chunks
{
sb.Clear();
for (int j = 0; j < 100; j++) // 100 lines each chunk
{
sb.AppendLine(random.Next().ToString());
}
buffer.Add(sb.ToString());
}
buffer.CompleteAdding();
}, TaskCreationOptions.LongRunning);
var consumer = Task.Factory.StartNew(() =>
{
using (var outputFile = new StreamWriter(@".\..\..\Huge.txt"))
foreach (var chunk in buffer.GetConsumingEnumerable())
{
outputFile.Write(chunk);
}
}, TaskCreationOptions.LongRunning);
Task.WaitAll(producer, consumer);
这样,您就不需要在随机行的生成中使用线程安全,因为生成是在单个线程中进行的。
更新:如果写入磁盘不是瓶颈,并且生产者的速度比使用者慢,则可以添加更多的生产者。贝娄是具有三个生产者和一个消费者的版本。
var buffer = new BlockingCollection<string>(boundedCapacity: 10);
var producers = Enumerable.Range(0, 3)
.Select(n => Task.Factory.StartNew(() =>
{
var random = new Random(n); // Non-random seed, same data on every run
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Clear();
for (int j = 0; j < 100; j++)
{
sb.AppendLine(random.Next().ToString());
}
buffer.Add(sb.ToString());
}
}, TaskCreationOptions.LongRunning))
.ToArray();
var allProducers = Task.WhenAll(producers).ContinueWith(_ =>
{
buffer.CompleteAdding();
});
// Consumer the same as previously (ommited)
Task.WaitAll(allProducers, consumer);