快速用随机数据填充大文件

时间:2019-07-05 12:01:39

标签: c#

我正在尝试用随机数据填充超大文件(> 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),我想不出任何最佳方法。

有人可以建议如何优化它吗?

1 个答案:

答案 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);