在C#中创建文件的最快方法

11

我正在运行一个程序,以测试在具有大量文件的文件夹中查找和迭代所有文件的速度。该过程中最慢的部分是创建100万多个文件。目前我使用的是相当幼稚的方法来创建这些文件:

Console.Write("Creating {0:N0} file(s) of size {1:N0} bytes... ", 
    options.FileCount, options.FileSize);
var createTimer = Stopwatch.StartNew();
var fileNames = new List<string>();
for (long i = 0; i < options.FileCount; i++)
{
    var filename = Path.Combine(options.Directory.FullName, 
                        CreateFilename(i, options.FileCount));
    using (var file = new FileStream(filename, FileMode.CreateNew, 
                        FileAccess.Write, FileShare.None, 4096, 
                        FileOptions.WriteThrough))
    {
        // I have an option to write some data to files, but it's not being used. 
        // That's why there's a using here.
    }
    fileNames.Add(filename);
}
createTimer.Stop();
Console.WriteLine("Done.");

// Other code appears here.....

Console.WriteLine("Time to  CreateFiles: {0:N3}sec ({1:N2} files/sec, 1 in {2:N4}ms)"
       , createTimer.Elapsed.TotalSeconds
       , (double)total / createTimer.Elapsed.TotalSeconds
       , createTimer.Elapsed.TotalMilliseconds / (double)options.FileCount);

输出:

Creating 1,000,000 file(s) of size 0 bytes... Done.
Time to  CreateFiles: 9,182.283sec (1,089.05 files/sec, 1 in 9.1823ms)

有什么比这更好的方法吗?我希望测试超过100万个数量级,但创建文件需要一整天的时间!我还没有尝试任何形式的并行处理,尝试优化任何文件系统选项或更改文件创建顺序。为了完整起见,这里是“CreateFilename()”的内容:
public static string CreateFilename(long i, long totalFiles)
{
    if (totalFiles < 0)
        throw new ArgumentOutOfRangeException("totalFiles", 
            totalFiles, "totalFiles must be positive");

    // This tries to keep filenames to the 8.3 format as much as possible.
    if (totalFiles < 99999999)
        // No extension.
        return String.Format("{0:00000000}", i);
    else if (totalFiles >= 100000000 && totalFiles < 9999999999)
    {
        // Extend numbers into extension.
        long rem = 0;
        long div = Math.DivRem(i, 1000, out rem);
        return String.Format("{0:00000000}", div) + "." + 
            String.Format("{0:000}", rem);
    }
    else
        // Doesn't fit in 8.3, so just tostring the long.
        return i.ToString();
}

更新

按照StriplingWarrior的建议尝试使用Parallel.For()进行并行化。结果:约30个线程在我的磁盘上疯狂运转,导致网络速度变慢!

        var fileNames = new ConcurrentBag<string>();
        var opts = new ParallelOptions();
        opts.MaxDegreeOfParallelism = 1;       // 1 thread turns out to be fastest.
        Parallel.For(0L, options.FileCount, opts,
            () => new { Files = new List<string>() },   
            (i, parState, state) =>
            {
                var filename = Path.Combine(options.Directory.FullName, 
                                   CreateFilename(i, options.FileCount));
                using (var file = new FileStream(filename, FileMode.CreateNew
                                  , FileAccess.Write, FileShare.None
                                  , 4096, FileOptions.WriteThrough))
                {
                }
                fileNames.Add(filename);
                return state;
            },
            state => 
            {
                foreach (var f in state.Files)
                {
                    fileNames.Add(f);
                }
            });
        createTimer.Stop();
        Console.WriteLine("Done.");

发现在FileStream中更改FileOptions可以提高性能约50%。看来我关闭了任何写缓存。
new FileStream(filename, FileMode.CreateNew, 
                 FileAccess.Write, FileShare.None, 
                 4096, FileOptions.None)

结果:

Creating 10,000 file(s) of size 0 bytes... Done.
Time to  CreateFiles: 12.390sec (8,071.05 files/sec, 1 in 1.2390ms)

欢迎提出其他想法。


5
你需要将它们全部写入一个目录吗?如果你能将它们分成1000个文件放在1000个目录中,速度可能会更快。 - Oded
3
就我所知,这几乎肯定是您特定磁盘/文件系统的限制,而不是C#的问题。使用SSD解决方案确实会有所帮助。 - Chris Shain
5
我觉得(尽管没有尝试过),“使用C语言”并不是一个有用的答案。C#文件操作只是对Win32方法的简单包装,因为磁盘IO将是你遇到的主要性能瓶颈,所以你不需要太关注额外开销。 - Ed S.
@ChrisShain - 我目前正在使用机械硬盘。将来我会尝试使用固态硬盘(并期望性能更好,尽管许多小写操作可能不太顺畅)。 - ligos
1
随机想法:NTFS的事务文件系统能帮上忙吗?有没有C#接口可以使用? - ligos
显示剩余4条评论
2个回答

10

在这里,您最大的瓶颈无疑是硬盘。通过利用并行处理,我经过一些快速测试能够看到一些显著的性能提升(但并不是数量级的)。

Parallel.For(1, 10000,
    i => File.Create(Path.Combine(path, i.ToString())));

有趣的是,至少在我的电脑上,SSD对此操作似乎没有太大的影响。

  • 使用我的HDD,上述代码在大约31秒内创建100,000个文件。
  • 使用我的SSD,上述代码在大约33秒内创建100,000个文件。

更新

十年后,随着硬件和.NET 6的更新,我决定使用基准测试来测试一些不同的策略。 LINQPad脚本

令人惊讶的是,Parallel.For方法似乎只需要1/3的时间。尝试通过WriteAllTextAsync利用并发性没有显著影响。

enter image description here


嗯...使用Parallel.For()我没有得到任何加速。但是你的代码确实运行得更快。一定是其他原因... - ligos
1
啊!!File.Create()比创建新的FileStream快一个数量级。 - ligos
1
哦,似乎从 File.Create() 返回的 FileStream 调用 Close()/Dispose() 也会有一些额外开销。最终发现在 FileStream 构造函数中传递 FileOptions.None 速度更快。现在我能够每秒创建 ~8k 个文件。 - ligos
1
由于瓶颈 应该是 文件系统,因此 Parallel.For 不太可能有所帮助。 但我可以看到 File.CreateFileStream.Create 更快。 - Jim Mischel
1
@JimMischel - 我检查了.NET 4中File.Create()的源代码,它只是创建了一个FileStream,所有的操作都在FileStream构造函数中。但那就是我发现FileOptions错误的地方。 - ligos
显示剩余3条评论

3
我找到的最快方法是简单循环调用File.Create()
IEnumerable filenames = GetFilenames();
foreach (var filename in filenames)
{
    File.Create(filename);
}

这与我实际在代码中使用的相等:

IEnumerable filenames= GetFilenames();
foreach (var filename in filenames)
{
    new FileStream(filename, FileMode.CreateNew, 
             FileAccess.Write, FileShare.None, 
             4096, FileOptions.None)
}

如果你真的想往文件中写入内容:

IEnumerable filenames= GetFilenames();
foreach (var filename in filenames)
{
    using (var fs = new FileStream(filename, FileMode.CreateNew, 
             FileAccess.Write, FileShare.None, 
             4096, FileOptions.None))
    {
        // Write something to your file.
    }
}

看起来没有用的东西:

  • Parallel.ForEach()Parallel.For()形式的并行性。这会产生一个净减速,随着线程数的增加而变得更糟。
  • 根据StriplingWarrior所说,SSD可能不会有帮助。我自己还没有测试过,但我推测这可能是因为有太多的小写入。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接