如何提高此 CopyTo 方法的性能?

Posted

技术标签:

【中文标题】如何提高此 CopyTo 方法的性能?【英文标题】:How can I improve the performance of this CopyTo method? 【发布时间】:2021-04-28 18:35:30 【问题描述】:

编辑:我现在已经解决了这个问题。我的答案贴在下面,当 SO 允许时将标记为已解决。

我有一个 CopyTo(和一个 CopyToAsync)方法来复制我的 C# 应用程序中的文件。 我发现与 Xcopy 相比,复制文件实际上很慢。

我提取了 copy 方法的核心功能并将其放入测试控制台应用程序中,以获得它与 Xcopy 的运行速度,发现结果实际上完全不同。

我得到的结果是:

异步方法:36.59 秒 - 平均速度:1512.63 mb/秒

同步方法:36.49 秒 - 平均速度:1516.72 mb/秒

XCOPY:5.62 秒 - 平均速度:9842.11 mb/秒

这三个都使用完全相同的文件和完全相同的目标。

StreamExtensions 类:

public static class StreamExtensions
    

        const int DEFAULT_BUFFER = 0x1000; // 4096 bits

        public static async Task CopyToAsync(this Stream source, Stream destination, IProgress<long> progress, CancellationToken cancellationToken = default, int bufferSize = DEFAULT_BUFFER)
        
            var buffer = new byte[bufferSize];
            int bytesRead;
            long totalRead = 0;

            while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
            
                await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();

                totalRead += bytesRead;
                progress.Report(totalRead);
            
        

        public static void CopyTo(this Stream source, Stream destination, IProgress<long> progress, int bufferSize = DEFAULT_BUFFER)
        
            var buffer = new byte[bufferSize];
            int bytesRead;
            long totalRead = 0;

            while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
            
                destination.Write(buffer, 0, bytesRead);

                totalRead += bytesRead;
                progress.Report(totalRead);
            
        
    

IProgress&lt;long&gt; 对象用于将文件进度报告回调用方法。

示例调用实现:

// Asynchronous version
public static async Task CopyFileSetAsync(Dictionary<string, string> fileSet)

    for (var x = 0; x < fileSet.Count; x++)
    
        var item = fileSet.ElementAt(x);
        var from = item.Key;
        var to = item.Value;

        int currentProgress = 0;

        long fileSize = new FileInfo(from).Length;

        IProgress<long> progress = new SynchronousProgress<long>(value =>
        
            decimal fileProg = (decimal)(value * 100) / fileSize;

            if (fileProg != currentProgress)
            
                currentProgress = (int)fileProg;
                OnUpdateFileProgress(null, new FileProgressEventArgs(fileProg));
            
        );

        using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
        
            using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
            
                await inStream.CopyToAsync(outStream, progress);
            
        

        OnUpdateFileProgress(null, new FileProgressEventArgs(100)); // Probably redundant
    


// Synchronous version
public static void CopyFileSet(Dictionary<string, string> fileSet)

    for (var x = 0; x < fileSet.Count; x++)
    
        var item = fileSet.ElementAt(x);
        var from = item.Key;
        var to = item.Value;

        int currentProgress = 0;

        long fileSize = new FileInfo(from).Length;

        IProgress<long> progress = new SynchronousProgress<long>(value =>
        
            decimal fileProg = (decimal)(value * 100) / fileSize;

            if (fileProg != currentProgress)
            
                currentProgress = (int)fileProg;
                OnUpdateFileProgress(null, new FileProgressEventArgs(fileProg));
            
        );

        using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
        
            using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
            
                inStream.CopyTo(outStream, progress, 1024);
            
        

        OnUpdateFileProgress(null, new FileProgressEventArgs(100)); // Probably redundant
    

有什么东西阻止它尽可能快地运行吗?我只是对它与复制相比慢了多少感到困惑。

编辑:修正了一个错字,我在 IProgress 周围忘记了一个 `

【问题讨论】:

更改缓冲区大小对所用时间有明显影响吗? DEFAULT_BUFFER = 0x1000; // 4096 bits。它们是字节,而不是 xcopy 使用与robocopy 相同的底层代码,并且可能并行复制文件。 按照 Tom 的建议,尝试将缓冲区大小增加到 1024 * 1024 (1mb) 64K 缓冲区怎么样?你也试过官方的FileStream.CopyTo,因为这可能避免了双重复制? 【参考方案1】:

感谢Tom 和xanatos,我回答了我自己的问题:

我误解了缓冲区大小的影响。我只达到了 8192 字节作为缓冲区大小。在采纳了他们的建议后,我将缓冲区大小增加到 1mb(1048576 字节),这对性能产生了巨大影响。

异步方法:5.57 秒 - 平均速度:9938.68 mb/秒

同步方法:5.52 秒 - 平均速度:10028.36 mb/秒

XCOPY:5.03 秒 - 平均速度:11007.84 mb/秒

【讨论】:

每个进程的峰值/平均内存/CPU 使用率是多少? 您可能会考虑将缓冲区大小向下移动,直到看到有害的差异(我猜使用 64 kbyte 缓冲区可能几乎一样快)。如果您可以将其降低到 85k,那么您留下超出大对象堆 我认为你的意思是 1MB 而不是 1mb。 @jwdonahue - 在所有 3 中,CPU 利用率都没有提高到 30% 以上。在此之前,怠速在 26-30% 之间 谢谢@Flydog57。在看到性能明显下降之前,我会考虑减小缓冲区大小。

以上是关于如何提高此 CopyTo 方法的性能?的主要内容,如果未能解决你的问题,请参考以下文章

如何提高 Sql server 中 Distinct Query 的性能

如何简化/提高这个 MySQL 查询的性能?

为啥传递的数组不带ref,在方法内部被CopyTo更改?

多个连接和联合的查询性能真的很差,有没有其他方法可以提高执行时间?

怎么提高电脑内存的性能及使用效率都有哪些方法

如何提高datapump操作性能