【问题标题】:Multithreading in .NET 4.0 and performance.NET 4.0 中的多线程和性能
【发布时间】:2010-01-12 16:06:28
【问题描述】:

我一直在玩弄 .NET 4.0 中的 Parallel 库。最近,我为我们的大型系统之一必须使用的一些不寻常的读/写操作开发了一个自定义 ORM。这允许我用属性装饰一个对象,并通过反射找出它必须从数据库中提取哪些列,以及它必须在写入时输出哪些 XML。

由于我设想这个包装器可以在许多项目中重复使用,所以我想尽可能地加快速度。该库主要用于 .NET Web 应用程序。我正在使用一次性控制台应用程序来测试框架,以查看我创建的类。

我现在已经吸取了多线程带来的开销的教训。多线程会导致它运行得更慢。从周围阅读,对于已经做了很长时间的人来说似乎很直观,但对我来说实际上是违反直觉的:如何同时运行一个方法 30次比连续运行 30 次要慢吗?

我认为我不会因为多个线程必须争夺同一个共享对象而导致问题(尽管我还不够擅长它还不能确定),所以我认为减速即将到来从产生所有这些线程的开销和运行时保持它们全部正常。所以:

  • 虽然我主要是作为一个学习练习来做的,这是悲观吗?对于琐碎的非 IO 任务,多线程是否过大?我的主要目标是速度,而不是 UI 或其他任何东西的响应能力。
  • 由于线程池中已经创建线程,在 IIS 中运行相同的多线程代码会导致其加速,而现在我使用的是控制台应用程序,我假设它是单线程的,直到我告诉它否则?我即将进行一些测试,但我认为我缺少一些基础知识来了解为什么这将是一种方式或另一种方式。我的控制台应用程序也在我的桌面上运行,具有两个内核,而用于 Web 应用程序的服务器会有更多内核,因此我可能也必须将其用作变量。

【问题讨论】:

    标签: multithreading optimization asp.net-4.0


    【解决方案1】:

    线程实际上并非全部同时运行。

    在台式机上,我假设您有一个双核 CPU(最多可能是四核)。这意味着只能同时运行 2/4 个线程。

    如果您生成了 30 个线程,操作系统将不得不在这 30 个线程之间进行上下文切换以保持它们全部运行。上下文切换非常昂贵,因此速度变慢。

    作为一个基本建议,如果您尝试优化计算,我的目标是每个 CPU 1 个线程。除此之外,您并没有真正做任何额外的工作,您只是在同一个 CPU 上交换线程。试着把你的电脑想象成里面只有有限数量的工人,你不能同时做比你可用的工人数量更多的工作。

    .net 4.0 并行任务库中的一些新功能允许您执行考虑线程数量可伸缩性的事情。例如,您可以创建一堆任务,任务并行库将在内部计算出您有多少可用 CPU,并优化创建/使用的线程数以免 CPU 过载,因此您可以创建 30 个任务,但在双核机器上,TP 库仍然只会创建 2 个线程,并将 .显然,当您在更大的机器上运行它时,它会很好地扩展。或者你可以使用ThreadPool.QueueUserWorkItem(...) 之类的东西来排队一堆任务,池将自动管理用于执行这些任务的线程数。

    是的,线程创建有很多开销,但是如果您使用 .net 线程池(或 4.0 中的并行任务库),.net 将管理您的线程创建,您实际上可能会发现它创建线程数少于您创建的任务数。它将在可用线程上内部交换您的任务。如果您确实想控制实际线程的显式创建,则需要使用 Thread 类。

    [一些 cpu 可以用线程做一些聪明的事情,并且每个 CPU 可以运行多个线程 - 请参阅 hyperthreading - 但请查看您的任务管理器,如果您有超过 4-8 个虚拟 CPU,我会感到非常惊讶今天的桌面]

    【讨论】:

    • 补充一点 Simon 所说的,最佳线程数很难找到,因为它通常取决于一般的系统负载和您的代码在做什么,我最好的建议是进行实验高负载,看看什么会产生最佳性能。
    • @Lazarus。是的,我完全同意,这是真的。它也可以是非常系统特定的。您可能希望以某种方式将其公开为设置,以便可以根据具体情况对其进行调整,并提供一些良好的默认值。
    【解决方案2】:

    这方面存在很多问题,因此了解幕后发生的事情是值得的。我强烈推荐 Joe Duffy 的“Windows 上的并发编程”一书和“Java 并发实践”一书。后者在编写多线程代码时需要了解处理器架构的级别。您将遇到的一个会损害您的代码的问题是缓存,或者更有可能是缺少缓存。

    如前所述,调度和运行线程会产生开销,但您可能会发现跨线程共享数据时会产生更大的开销。这些数据可能会从处理器缓存刷新到主内存中,这会导致代码严重变慢。

    这是托管环境应该保护我们免受的那种低级别的东西,但是,在编写高度并行的代码时,这正是你必须处理的那种问题。

    我的一位同事录制了有关 Parallel.For 和 Parallel.ForEach 的性能问题的截屏视频,这可能会有所帮助:

    http://rocksolidknowledge.com/ScreenCasts.mvc/Watch?video=ParallelLoops.wmv

    【讨论】:

      【解决方案3】:

      您说的是 ORM,所以我推测一定数量的 I/O 正在进行。如果是这种情况,线程创建和上下文切换的开销将相对不存在。

      您很可能会遇到 I/O 争用:如果您无序读取同一组数据,则读取速度可能会比您更慢(尤其是在旋转硬盘驱动器上,但在其他存储设备上也是如此)。按顺序阅读。因此,如果您正在执行 30 个数据库查询,如果它们都由同一个 I/O 设备支持并且查询不在缓存中,那么它们可能会比并行运行更快。并行运行它们可能会导致系统几乎同时有一堆 I/O 读取请求,这可能会导致操作系统依次读取每个请求的一小部分 - 导致您的驱动器磁头来回跳跃,浪费宝贵的毫秒。

      但这只是猜测;在不了解更多信息的情况下,无法真正确定是什么导致您的速度变慢。

      虽然与添加两个数字相比,创建线程“非常昂贵”,但通常不会轻易过度。如果您的操作非常短(例如,一毫秒或更短),使用线程池而不是新线程将显着节省时间。一般来说,如果你的操作很短,你应该重新考虑并行的粒度;也许您最好将计算拆分成更大的块:例如,通过使用相当少的工作任务来一次处理整批较小的工作项,而不是单独处理每个项。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-05
        • 1970-01-01
        • 1970-01-01
        • 2012-09-01
        相关资源
        最近更新 更多