C#:具有大量I / O的多线程性能不佳

时间:2009-03-28 14:45:06

标签: c# performance multithreading

我在C#中编写了一个应用程序,它将jpgs从一组目录同时移动到另一组目录(每个固定子目录一个线程)。代码看起来像这样:

        string destination = "";
        DirectoryInfo dir = new DirectoryInfo("");
        DirectoryInfo subDirs = dir.GetDirectories();
        foreach (DirectoryInfo d in subDirs)
        {
            FileInfo[] files = subDirs.GetFiles();
            foreach (FileInfo f in files)
            {
                f.MoveTo(destination);
            }
        }

然而,应用程序的性能是可怕的 - 大量的页面错误/秒。每个子目录中的文件数量可能会非常大,所以我认为一个很大的性能损失来自一个上下文切换,它不能同时将所有不同的文件阵列保存在RAM中,这样它就会进入磁盘几乎每一次。

我能想到两种不同的解决方案。第一种是用C或C ++重写它,第二种是使用多个进程而不是多线程。

编辑:文件根据时间戳命名,它们移动到的目录基于该名称。因此,它们被移动到的目录将对应于它创建的小时;例如3-27-2009 / 10。

我们正在为每个目录创建一个后台工作程序来进行线程化。

有什么建议吗?

8 个答案:

答案 0 :(得分:18)

经验法则,不要将操作与串行依赖关系并行化。在这种情况下,您的硬盘驱动器是瓶颈,许多线程只会使性能变差。

如果您要使用线程,请尝试将数量限制为您可用的资源数量,核心和硬盘数量不是您要挂起的作业数量,要复制的目录。

答案 1 :(得分:7)

重新考虑回答

我一直在重新考虑下面的原始答案。我仍然怀疑使用更少的线程可能是一个好主意,但是因为你只是移动文件,它实际上不应该是IO密集型。 列出文件可能会占用大量磁盘。

但是,我怀疑你的文件内存真的不足。你有多少记忆力?这个过程占用了多少内存?您使用了多少个线程,以及您拥有多少个核心? (使用明显多于核心的线程是一个坏主意,IMO。)

我建议采取以下攻击计划:

  • 找出实际存在瓶颈的地方。尝试获取文件列表但不进行移动。看看磁盘有多难以及需要多长时间。
  • 尝试不同数量的线程,目录队列仍有待处理。
  • 密切关注内存使用和垃圾收集。 CLR的Windows性能计数器对此有好处。

原始回答

用C或C ++重写无济于事。使用多个过程无济于事。你正在做的就是给一个处理器一百个线程 - 除了你用磁盘做它。

如果涉及相当多的计算,并行使用IO的任务是有意义的,但是如果它已经是磁盘绑定的,那么要求磁盘同时处理大量文件是只会让事情变得更糟。

您可能对我最近运行的基准测试(descriptioninitial results感兴趣,测试文件各行的“加密”。当“加密”级别较低时(即它几乎不做任何CPU工作),最好的结果总是只有一个线程。

答案 2 :(得分:6)

如果您的工作块依赖于系统瓶颈,在这种情况下是磁盘IO,那么最好不要使用多个线程或进程。您最终将要做的就是在等待磁盘时产生大量额外的CPU和内存活动。如果您使用单个线程进行移动,您可能会发现应用程序的性能得到了改善。

答案 3 :(得分:2)

看来你正在移动目录,当然只需重命名/移动目录即可。如果你使用相同的源和硬盘,那将是即时的。

同样捕获每个文件的所有文件信息都是不必要的,文件的名称就足够了。

答案 4 :(得分:1)

性能问题来自硬盘驱动器用C / C ++做任何事情都没有意义,也没有从多个进程做任何事情

答案 5 :(得分:1)

您是否正在查看页面错误计数并从中推断内存压力?您可能会发现底层的Win32 / OS文件副本使用映射文件/页面错误来完成其工作,并且故障并不是问题的标志。 Window的大部分文件处理是通过页面错误完成的(例如'加载'可执行代码) - 它们本身并不是坏事。

如果 遭受内存压力,那么我猜测它更可能是由创建大量线程(非常昂贵)而不是文件复制引起的。

如果没有分析,请不要更改任何内容,如果您分析并发现时间花在框架方法上,这些方法只是Win32函数的包装器(下载框架源并查看这些方法是如何工作的),那么就不要浪费时间在C ++上。

答案 6 :(得分:0)

如果GetFiles()确实返回了大量数据,您可以编写一个枚举器,如:

IEnumerable<string> GetFiles();

答案 7 :(得分:0)

那么,您是将文件从一个子文件夹一次一个地移动到另一个子文件夹?当驱动器头来回移动时,你不会引起大量的磁盘搜索吗?通过将文件读入内存可以获得更好的性能(至少批量生成,如果不是全部一次),将它们写入磁盘,然后从磁盘中删除原件。

如果您在不同的线程中执行多组文件夹,那么您将更多地移动磁盘头。这是多个线程对你没有帮助的一种情况(尽管如果你有RAID或SAN,你可能会得到一些好处)。

如果您以某种方式处理文件,那么如果不同的CPU可以同时计算多个文件,则mulptithreading可能会有所帮助。但是你不能让四个CPU同时将一个磁盘头移动到四个不同的位置。

相关问题