【问题标题】:Why .NET async await file copy is a lot more CPU consuming than synchronous File.Copy() call?为什么 .NET 异步等待文件复制比同步 File.Copy() 调用更消耗 CPU?
【发布时间】:2017-07-13 05:30:54
【问题描述】:

为什么下面的代码会导致:

public static class Program
{
    public static void Main(params string[] args)
    {
        var sourceFileName = @"C:\Users\ehoua\Desktop\Stuff\800MFile.exe";
        var destinationFileName = sourceFileName + ".bak";

        FileCopyAsync(sourceFileName, destinationFileName);

        // The line below is actually faster and a lot less CPU-consuming
        // File.Copy(sourceFileName, destinationFileName, true);

        Console.ReadKey();
    }

    public static async void FileCopyAsync(string sourceFileName, string destinationFileName, int bufferSize = 0x1000, CancellationToken cancellationToken = default(CancellationToken))
    {
        using (var sourceFile = File.OpenRead(sourceFileName))
        {
            using (var destinationFile = File.OpenWrite(destinationFileName))
            {
                Console.WriteLine($"Copying {sourceFileName} to {destinationFileName}...");
                await sourceFile.CopyToAsync(destinationFile, bufferSize, cancellationToken);
                Console.WriteLine("Done");
            }
        }
    }
}

而 File.Copy(): https://msdn.microsoft.com/en-us/library/system.io.file.copy(v=vs.110).aspx 的 CPU 消耗要少得多:

那么,使用 async / await 来复制文件还有真正的兴趣吗?

我认为保存用于复制的线程可能值得,但 File.Copy 窗口功能似乎在 CPU 百分比方面赢得了胜利。有些人会争辩说这是因为真正的 DMA 支持,但是,我是否在做任何事情来破坏表演?或者有什么办法可以通过我的异步方法提高 CPU 使用率?

【问题讨论】:

  • 这可能与实现File.Copy 的一些底层方式与使用File.OpenReadFile.OpenWrite 的直接基于流的方法之间的区别有关。我怀疑它与同步与异步有什么关系。
  • 就像我说的,我怀疑它是否与异步有关,而是与 如何 执行复制操作有关。您的 async 方法从一个流复制到另一个流,而您的 sync 方法使用 File.Copy 包装本机 Win32 操作以实现更底层的方法。见stackoverflow.com/questions/1246899/…
  • 您的问题是:我要求编译器在等待 IO 操作时生成可以利用 CPU 的代码;为什么我这样做的时候 CPU 使用率更高? 当你这样说的时候问题就自己回答了,不是吗?你认为异步是 for 的什么?这是为了增加等待 IO 时使用的 CPU 量。请记住,CPU 利用率高是好的。人们说它很糟糕,但高 CPU 很棒。机器所有者为该 CPU 付费;它空闲的每一毫秒都是一种资源的浪费。
  • 好吧,也许这里发生了其他事情;正如其他人所说,您可能会从病毒检查程序或其他东西中获得一些短暂的影响。特别是因为您的程序在等待时似乎没有做任何事情。但总的来说,您应该期望在使用异步时看到 更好 -- 更高 -- CPU 利用率。不要停止 CPU;继续努力解决 CPU 密集型问题。
  • @TheodorZoulias:是的,正如我在评论中提到的,我们想要的是 CPU 高效地工作以完成 CPU 密集型工作。虽然我们很挑剔,但我注意到 Windows 上的 .NET 线程需要 1MB 的已提交虚拟地址空间;操作系统足够聪明,除非必要,否则不会将其映射到 RAM。大多数情况下,最好将其视为 1MB 的交换文件,而不是 RAM。

标签: c# .net asynchronous io file-copying


【解决方案1】:

这些都是相当荒谬的性能数字。你根本没有衡量你认为自己是什么。这应该不会超过一个小问题,即缓存文件数据的简单内存到内存复制。就像 File.Copy() 一样。在具有良好 DDR3 RAM 的机器上以约 35 GB/秒的速度运行,因此不会超过几十毫秒。即使文件没有被缓存或者机器没有足够的内存那么你仍然无法获得这种 CPU 负载,你的代码会被阻塞等待磁盘。

实际上看到的是您安装的反恶意软件产品的性能。当它看到操纵可执行文件的程序时,它总是会束手无策。

验证、禁用或排除并重试很简单。

【讨论】:

  • 同意VS“恶意软件”,不过,我要指出的是,任务管理器仍然显示cpu %有一定的增加(加上我的廉价粉丝笔记本电脑在使用异步等待时会遭受更多的痛苦)。再次是的,File.Copy 的实现当然更好,我管理器通过使用 isAsync 参数将异步实现 cpu % 降低了大约 75%。我将在 GitHub 上查看代码 corefx,因为这对于幕后发生的事情仍然有点模糊。无论如何,感谢您对测试环境的了解。
  • “您实际看到的是您安装的反恶意软件产品的性能。” ——你能澄清一下你的意思吗?您是说反恶意软件产品正在使用 Visual Studio 中显示的一堆 CPU?或者你是说另一个程序导致@EhouarnPerret 的程序本身使用更多的CPU?我刚刚打开了 Prime95,它在任务管理器中将我的所有 8 个内核都固定在 100%,但 Visual Studio 仍然显示 ~13% 的 CPU 使用率,与任务管理器为我的 1 个“ConsoleApplication”进程显示的数字相同。
  • 您通常看不到反恶意软件引起的开销,因为您没有分析器观察程序正在做什么的奢侈。不要射击信使 :) 如果您有其他不信任分析器的情况,请单击该按钮。
【解决方案2】:

File.OpenRead(sourceFileName) 等价于new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read),后者又等价于public FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false),也就是说false 用于异步I/O。 File.OpenWrite 的等价物也是如此。

因此,任何XXXAsync 操作都不会使用异步 I/O,而是会使用线程池线程来伪造它。

所以它没有获得异步 I/O 的好处,并且至少浪费了一个线程。您在 I/O 上有一个额外的线程阻塞,这是您想要避免的。我通常希望 async 本身的执行速度比 sync 稍慢(async 通常会牺牲一次性速度以获得更好的可扩展性),但我绝对希望这比将整个事情包装在 @ 中做得更好,如果有的话987654327@.

我仍然不认为它会那么糟糕,但也许反恶意软件正在通过写入 .exe 来担心。

希望您可以更好地复制非 exe 和异步流。

【讨论】:

  • 其实我在评论 Hans 的答案时提到过,isAsync 参数一旦设置为 true 会显着降低 CPU 百分比,但速度似乎慢了很多(我花了一段时间,因为我没有进行基准测试起初文件复制速度,但似乎 File.Copy 显然比 async await 快 2-3 倍,但又要加点盐)。明天我将通过适当的基准测试更新帖子。我接受你的回答,因为除了 Eric Lippert 的 cmets 之外,这很有意义。
  • 我不会对异步复制比同步慢几倍感到恐惧。当我想要更高的单操作吞吐量时,我不会使用它;当我想同时做其他事情时,我会使用它,或者当我不在乎我为单个请求提供多快的速度时,我会在 Web 应用程序上使用它。我可以同时服务多少和多少。
【解决方案3】:

File.Copy 似乎可以一次性复制整个文件。 使用 FileStreams,默认缓冲区大小为 4096 字节,因此一次复制 4kb。

我编写了自己的 async 函数,它不仅仅复制文件(它匹配文件大小并进行清理),但这里是通过 50mbps 宽带链接通过 VPN 对文件复制进行基准测试的结果。

当使用默认的 4096 字节时我的async 文件复制:

Copy of 52 files via CopyFileAsync() took 5.6 minutes

对比

File.Copy 需要

Copy of 52 files via File.Copy() took 24 secs, 367 ms

当我将缓冲区大小增加到 64KB 时,我得到以下信息

Copy of 52 files via CopyFileAsync() took 39 secs, 407 ms

底线是 4096 的默认缓冲区大小对于现代硬件来说太小了,这就是为什么它通过流复制如此缓慢的原因。您需要针对将要使用的硬件进行基准测试,以确定最佳缓冲区大小,但一般而言,64K 对于互联网上的网络流量来说是相当最佳的。

【讨论】:

猜你喜欢
  • 2014-10-23
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-15
  • 2013-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多