C# 如何优化计算数千个文件的散列?

C# 如何优化计算数千个文件的散列?,c#,.net,file,hash,C#,.net,File,Hash,我有一个.Net程序,它运行在一个包含成千上万个相对较小的文件(每个文件大约10MB)的目录中,计算它们的MD5散列并将数据存储在SQLite数据库中。整个过程运行良好,但需要相对较长的时间(1094353ms,约60000个文件),我正在寻找优化方法。以下是我想到的解决方案: 使用其他线程并同时计算多个文件的哈希。不确定I/O速度会如何限制我使用这个 使用更好的散列算法。我环顾四周,发现我目前使用的那个似乎是最快的(至少在C#上) 哪种方法是最好的,还有更好的方法吗 以下是我当前的代码: pr

我有一个.Net程序,它运行在一个包含成千上万个相对较小的文件(每个文件大约10MB)的目录中,计算它们的MD5散列并将数据存储在SQLite数据库中。整个过程运行良好,但需要相对较长的时间(1094353ms,约60000个文件),我正在寻找优化方法。以下是我想到的解决方案:

  • 使用其他线程并同时计算多个文件的哈希。不确定I/O速度会如何限制我使用这个

  • 使用更好的散列算法。我环顾四周,发现我目前使用的那个似乎是最快的(至少在C#上)

  • 哪种方法是最好的,还有更好的方法吗

    以下是我当前的代码:

    private async Task<string> CalculateHash(string file, System.Security.Cryptography.MD5 md5) {
        Task<string> MD5 = Task.Run(() =>
        {
            {
                using (var stream = new BufferedStream(System.IO.File.OpenRead(file), 1200000))
                    {
                        var hash = md5.ComputeHash(stream);
                        var fileMD5 = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2")));
    
                        return fileMD5;
                    }
                };
            });
    
            return await MD5;
        }
    
    public async Main() {
        using (var md5 = System.Security.Cryptography.MD5.Create()) {
             foreach (var file in Directory.GetFiles(path)) {
                var hash = await CalculateHash(file, md5);
    
                // Adds `hash` to the database
            }
        }
    }
    
    private async Task CalculateHash(字符串文件,System.Security.Cryptography.MD5-MD5){
    任务MD5=任务。运行(()=>
    {
    {
    使用(var stream=new BufferedStream(System.IO.File.OpenRead(File),1200000))
    {
    var hash=md5.ComputeHash(流);
    var fileMD5=string.Concat(Array.ConvertAll(散列,x=>x.ToString(“X2”));
    返回文件md5;
    }
    };
    });
    返回等待MD5;
    }
    公共异步Main(){
    使用(var md5=System.Security.Cryptography.md5.Create()){
    foreach(Directory.GetFiles(path)中的var文件){
    var hash=await CalculateHash(文件,md5);
    //将“哈希”添加到数据库中
    }
    }
    }
    
    据我所知,Task.Run将为每个文件实例化一个新线程,这将导致大量线程和它们之间的上下文切换。正如您所描述的案例,听起来是使用Parallel.for或Parallel.Foreach的好案例,如下所示:

    public void CalcHashes(string path)
    {
        string GetFileHash(System.Security.Cryptography.MD5 md5, string fileName)
        {
            using (var stream = new BufferedStream(System.IO.File.OpenRead(fileName), 1200000))
            {
                var hash = md5.ComputeHash(stream);
                var fileMD5 = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2")));
    
                return fileMD5;
            }
        }
    
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = 8;
    
        Parallel.ForEach(filenames, options, fileName =>
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                GetFileHash(md5, fileName);
            }
        });
    }
    
    编辑:似乎是并行的。ForEach实际上并不会自动进行分区。将最大并行度限制添加到8。因此: 107005个文件
    46628 ms

    据我所知,Task.Run将为每个文件实例化一个新线程,这将导致大量线程和它们之间的上下文切换。正如您所描述的案例,听起来是使用Parallel.for或Parallel.Foreach的好案例,如下所示:

    public void CalcHashes(string path)
    {
        string GetFileHash(System.Security.Cryptography.MD5 md5, string fileName)
        {
            using (var stream = new BufferedStream(System.IO.File.OpenRead(fileName), 1200000))
            {
                var hash = md5.ComputeHash(stream);
                var fileMD5 = string.Concat(Array.ConvertAll(hash, x => x.ToString("X2")));
    
                return fileMD5;
            }
        }
    
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = 8;
    
        Parallel.ForEach(filenames, options, fileName =>
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                GetFileHash(md5, fileName);
            }
        });
    }
    
    编辑:似乎是并行的。ForEach实际上并不会自动进行分区。将最大并行度限制添加到8。因此: 107005个文件
    46628 ms

    创建工作管道,我所知道的创建管道的最简单方法是使用代码中必须是单线程的部分和必须是多线程的部分


    创建一个工作管道,我知道如何创建一个同时使用代码中必须是单线程的部分和必须是多线程的部分的管道的最简单方法是使用



    1094353ms,大约有6万个文件
    …这是一种奇数有效数字样式;-)当你这样想的时候,它真的不是很重要,但是考虑到大部分文件的大小甚至不超过200 KB,我认为需要进行一些优化。IO可能是一个相当大的障碍。也许最好先测量IO负载,以确定这是否是一个瓶颈。话虽如此,(在我看来)这似乎是典型的一次性操作。你确定你想优化它到目前为止吗?你是对的,这是一个一次性的操作,但最好不要让用户在第一次启动我的程序时等待18分钟:P我将监视I/O负载,看看这是否是瓶颈,然后报告。这类事情通常在后台运行。音乐播放器或randsomewarez XD等设备往往允许您在进行后台索引时使用该程序。
    1094353ms,包含约6万个文件
    …这是一种奇怪的有效数字样式;-)当你这样想的时候,它真的不是很重要,但是考虑到大部分文件的大小甚至不超过200 KB,我认为需要进行一些优化。IO可能是一个相当大的障碍。也许最好先测量IO负载,以确定这是否是一个瓶颈。话虽如此,(在我看来)这似乎是典型的一次性操作。你确定你想优化它到目前为止吗?你是对的,这是一个一次性的操作,但最好不要让用户在第一次启动我的程序时等待18分钟:P我将监视I/O负载,看看这是否是瓶颈,然后报告。这类事情通常在后台运行。像音乐播放器或randsomewarez XD这样的东西往往允许您在后台索引时使用该程序。通常,并行运行多个I/O操作会使速度变慢。除非你们确定这个问题在其他地方有瓶颈,否则建议并行运行多个读取是没有帮助的。为了以防万一,我们将对其进行基准测试。我将报告结果。已经做了,似乎阿列克谢是对的,平行foreach实际上让事情变慢了:)啊,太糟糕了。无论如何,谢谢你的建议,编辑了答案。对于Parallel.ForEach,似乎必须明确限制它创建的线程数。有了这个限制,它实际上比a)简单的无线程版本和b)为每个迭代版本生成一个线程要快得多。通常,并行运行多个I/O操作会使速度变慢。除非你们确定这个问题在其他地方有瓶颈,否则建议并行运行多个读取是没有帮助的。为了以防万一,我们将对其进行基准测试。我将报告结果。已经做了,似乎阿列克谢是对的,平行foreach实际上让事情变慢了:)啊,太糟糕了。无论如何,谢谢你的建议,编辑了答案。对于Parallel.ForEach,似乎必须明确限制它创建的线程数。有了这个限制,它实际上比a)快了很多
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;
    
    public static class Example
    {
        private class Dto
        {
            public Dto(string filePath, byte[] data)
            {
                FilePath = filePath;
                Data = data;
            }
    
            public string FilePath { get; }
            public byte[] Data { get; }
        }
    
        public static async Task ProcessFiles(string path, IProgress<ProgressReport> progress)
        {
            int totalFilesFound = 0;
            int totalFilesRead = 0;
            int totalFilesHashed = 0;
            int totalFilesUploaded = 0;
    
            DateTime lastReported = DateTime.UtcNow;
    
            void ReportProgress()
            {
                if (DateTime.UtcNow - lastReported < TimeSpan.FromSeconds(1)) //Try to fire only once a second, but this code is not perfect so you may get a few rapid fire.
                {
                    return;
                }
                lastReported = DateTime.UtcNow;
                var report = new ProgressReport(totalFilesFound, totalFilesRead, totalFilesHashed, totalFilesUploaded);
                progress.Report(report);
            }
    
    
            var getFilesBlock = new TransformBlock<string, Dto>(filePath =>
            {
                var dto = new Dto(filePath, File.ReadAllBytes(filePath));
                totalFilesRead++; //safe because single threaded.
                return dto;
            });
    
            var hashFilesBlock = new TransformBlock<Dto, Dto>(inDto =>
                {
                    using (var md5 = System.Security.Cryptography.MD5.Create())
                    {
                        var outDto = new Dto(inDto.FilePath, md5.ComputeHash(inDto.Data));
                        Interlocked.Increment(ref totalFilesHashed); //Need the interlocked due to multithreaded.
                        ReportProgress();
                        return outDto;
                    }
                },
                new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism = Environment.ProcessorCount, BoundedCapacity = 50});
    
            var writeToDatabaseBlock = new ActionBlock<Dto>(arg =>
                {
                    //Write to database here.
                    totalFilesUploaded++;
                    ReportProgress();
                },
                new ExecutionDataflowBlockOptions {BoundedCapacity = 50});
    
            getFilesBlock.LinkTo(hashFilesBlock, new DataflowLinkOptions {PropagateCompletion = true});
            hashFilesBlock.LinkTo(writeToDatabaseBlock, new DataflowLinkOptions {PropagateCompletion = true});
    
            foreach (var filePath in Directory.EnumerateFiles(path))
            {
                await getFilesBlock.SendAsync(filePath).ConfigureAwait(false);
                totalFilesFound++;
                ReportProgress();
            }
    
            getFilesBlock.Complete();
    
            await writeToDatabaseBlock.Completion.ConfigureAwait(false);
            ReportProgress();
        }
    }
    
    public class ProgressReport
    {
        public ProgressReport(int totalFilesFound, int totalFilesRead, int totalFilesHashed, int totalFilesUploaded)
        {
            TotalFilesFound = totalFilesFound;
            TotalFilesRead = totalFilesRead;
            TotalFilesHashed = totalFilesHashed;
            TotalFilesUploaded = totalFilesUploaded;
        }
    
        public int TotalFilesFound { get; }
        public int TotalFilesRead{ get; }
        public int TotalFilesHashed{ get; }
        public int TotalFilesUploaded{ get; }
    }