【问题标题】:Threading vs single thread线程与单线程
【发布时间】:2010-05-25 05:35:34
【问题描述】:

是否总是保证多线程应用程序比单线程应用程序运行得更快?

我有两个线程从一个数据源填充数据,但不同的实体(例如:数据库,来自两个不同的表),似乎应用程序的单线程版本比具有两个线程的版本运行得更快。

为什么会这样?当我查看性能监视器时,两个 cpu 都非常刺眼?这是由于上下文切换吗?

有哪些最佳实践可以提升 CPU 并充分利用它?

我希望这不是模棱两可的。

【问题讨论】:

    标签: c# .net multithreading cpu


    【解决方案1】:

    类比可能会有所帮助。

    您有一堆信件需要送到镇上的各个地址。所以你雇了一个骑摩托车的人来送你的信。

    您所在城镇的交通信号灯是完美的交通信号灯。除非有人在十字路口,否则它们始终是绿色的。

    摩托车上的那个人匆匆忙忙地送来一堆信。由于路上没有其他人,所以每盏灯都是绿色的,这太棒了。但是你认为嘿,这可能会更快。我知道,我会再雇一个司机。

    问题是**你只有一辆摩托车*。所以现在你的第一个司机骑着摩托车兜了一圈,然后时不时停下、下车,然后第二个司机跑起来、上车、兜兜转转。

    这会更快吗? 不,当然不会。这。添加更多线程不会使任何事情变得更快。线程不是魔法。如果一个处理器每秒能够执行十亿次操作,那么添加另一个线程并不会突然使每秒另外十亿次操作可用。相反,它窃取来自其他线程的资源。如果摩托车能以每小时 100 英里的速度行驶,那么停下自行车并让另一个司机上车并不能让它更快!显然,平均而言,在此方案中,信件的送达速度并没有提高,它们只是以不同的顺序送达。

    好的,那如果你雇两个司机和两辆摩托车呢?现在你有两个处理器,每个处理器一个线程,这样会更快,对吧?不,因为我们忘记了红绿灯。以前,任何时候都只有一辆摩托车高速行驶。现在有两个司机和两辆摩托车,这意味着现在有时其中一辆摩托车必须等待,因为另一辆在十字路口。同样,添加更多线程会减慢您的速度,因为您会花费更多时间来争用锁。添加的处理器越多,情况就越糟糕;你最终会花费越来越多的时间等待红灯,而越来越少的时间花在传递信息上。

    添加更多线程可能会导致可伸缩性,如果这样做会导致锁被争用。线程越多,争用越多,速度越慢。

    假设您使引擎更快——现在您拥有更多处理器、更多线程和更快的处理器。这总是让它更快吗?不。它经常没有。提高处理器速度会使多线程程序运行变慢。再次,想想交通。

    假设您有一个城市,有数千名司机和 64 辆摩托车,司机都在摩托车之间来回奔跑,一些在十字路口的摩托车挡住了其他摩托车。现在你让所有这些摩托车跑得更快。这有帮助吗?好吧,在现实生活中,当您四处行驶时,您驾驶保时捷的速度是否是本田思域的两倍?当然不是;在城市驾驶的大部分时间里,您堵在路上

    如果你能开得更快,通常你最终会在交通拥堵中等待更长的时间,因为你最终会更快地驶入拥堵。 如果每个人都更快地驶向拥堵,那么拥堵会变得更糟

    多线程性能可能非常违反直觉。如果你想要极高的性能,我建议不要使用多线程解决方案,除非你有一个“令人尴尬的并行”应用程序——也就是说,某些应用程序显然可以抛出多个处理器,比如计算Mandelbrot 设置或进行光线追踪或类似的事情。然后,不要在问题上抛出比处理器更多的线程。但是对于许多应用程序,启动更多线程会减慢您的速度

    【讨论】:

    • 是的,我完全同意你在这里提出的观点。虽然公平地说,我希望大多数“现实世界”的应用程序都是 IO 绑定的。因此,为了扩展您的类比,这就像每个驾驶员每天需要 8 小时的睡眠(尽管在 CPU 方面它更像是 100000 小时),这意味着您可以轻松地共享车辆。您最后一次看到您的 CPU 利用率接近 100% 是什么时候?
    • @CurtainDog:好点。但是,我一直看到我的 CPU 是 100%;我从事编译器工作。现代编译器几乎总是受 CPU 限制。一旦我们在内存中获得了源代码和元数据,它就会在内存中的数据结构上翻腾。
    • 还可能值得一提的是阿姆达尔定律,它告诉我们在并行计算中使用多个处理器的程序的加速受到连续分数部分所需时间的限制。 en.wikipedia.org/wiki/Amdahl%27s_law
    • 这些人应该使用电子邮件,而不是信件。
    【解决方案2】:

    我的意见

    不,不能保证多线程应用程序会比单线程应用程序运行得更快。主要问题是如何将工作负载正确分配到所有可用的内核并最大限度地减少锁定和上下文切换。

    我认为人们可以做的一些更糟糕的事情是去尝试多线程每一点他们的 CPU 密集型任务。有时他们最终会创建数百个线程,每个线程都试图执行大量 CPU 密集型计算。在这种情况下,最好的办法是为每个内核创建一个(或也许两个)线程。

    在涉及 UI 的情况下,几乎总是首选将所有 CPU 密集型工作委托给线程,以保持 UI 响应。这可能是线程最流行的用途。

    ...看起来像单线程版本 该应用程序的运行速度比 有两个线程的版本。

    您是否进行过任何性能分析?如果你没有,那么你所观察到的就有些无关紧要了。

    千斤顶的最佳做法是什么 CPU 并充分利用它?

    鉴于您对问题的描述,您的性能问题似乎不是 CPU 限制,而是 I/O 限制...您与数据库的通信比您的处理器缓存慢很多,如果它是网络数据库,那么它甚至比你的硬盘慢。您的性能瓶颈在于您的数据库,因此您需要做的就是创建足够多的线程来最大化您与数据库的连接的吞吐量。


    直接来自Wikipedia

    优势

    一些优点包括:

    • 如果一个线程出现大量缓存未命中,则其他线程可以继续,利用未使用的计算资源,从而提高整体执行速度,因为如果只有一个线程,这些资源就会处于空闲状态线程已执行。
    • 如果一个线程不能使用 CPU 的所有计算资源(因为指令依赖于彼此的结果),运行另一个线程允许不要让这些空闲。
    • 如果多个线程处理同一组数据,它们实际上可以共享它们的缓存,从而更好地使用缓存或同步其值。

    缺点

    对多线程的一些批评包括:

    • 在共享硬件资源(例如缓存或转换后备缓冲区 (TLB))时,多个线程可能会相互干扰。
    • 单线程的执行时间没有提高,但可能会降低,即使只有一个线程正在执行。这是因为频率较慢和/或需要额外的流水线阶段来适应线程切换硬件。
    • 对多线程的硬件支持对软件来说更明显,因此需要对应用程序和操作系统进行比多处理更多的更改。

    更新

    另外,数据库服务器位于 代码正在运行的同一台机器。 它不是一个 sql server。这是一个nosql 数据库管理系统。所以请不要假设任何事情 关于数据库服务器。

    一些 NoSQL 系统是基于磁盘的,从多个线程读取磁盘几乎肯定会降低性能。在线程之间跳转时,硬盘可能不得不将磁头移动到磁盘的不同扇区,这很糟糕!

    我明白你想表达的意思 make 是 IO 速度。但它仍然是 同一台机器。为什么 IO 这么慢?

    您的 NoSQL 系统可能是基于磁盘的,因此您的所有数据都存储在磁盘上,而不是加载到内存中(如 SQL Server)。进一步考虑架构:磁盘是 RAM 的缓存,RAM 是 CPU 缓存的缓存,CPU 缓存是 CPU 寄存器的缓存。所以磁盘 -> Ram -> CPU 缓存 -> 寄存器,在到达寄存器之前有 3 级缓存。根据您使用的数据量,您可能会在每个级别的两个线程中获得大量缓存未命中... CPU 缓存中的缓存未命中将从 RAM 加载更多数据,缓存未命中RAM 将从磁盘加载更多数据,所有这些都转化为吞吐量降低

    在其他评论家中“创造足够 线程利用..”创建许多 线程也需要时间。对吧?

    不是真的...你只有两个线程。您创建线程多少次?你多久创建一次?如果您只创建两个线程,并且在应用程序的整个生命周期中都在这两个线程中完成所有工作,那么创建线程几乎不会产生您应该关注的性能开销。

    【讨论】:

    • 性能分析是多次运行程序,记录单线程和多线程版本的运行时间。
    • @user177883 如果您能给我们一些代码 sn-ps 来向我们展示您在做什么,那就太好了,因为您在进行性能分析时会看到性能下降。一般来说,频繁的锁定和过多的线程(即 CPU 密集型任务的每个内核超过 1 个,或 i/o 密集型任务的每个 i/o 通道超过 1 个)会降低性能。确保您的数据库不会因来自多个线程的请求而不堪重负。
    • 就像我说的,我有两个线程 A,B,A 从存储 A_S 填充,B 从存储 B_S 填充。我将两个列表传递给线程以将数据存储在其中。仅此而已。
    • 另外,数据库服务器与代码运行在同一台机器上。它不是一个 sql server。这是一个nosql dbms。所以请不要假设任何关于数据库服务器的事情。我了解您要提出的观点是 IO 速度。但它仍然是同一台机器。为什么 IO 这么慢?在其他评论家中“创建足够的线程来使用..”创建许多线程也需要时间。对吗?
    【解决方案3】:

    如果您的程序 I/O 繁重并且大部分时间都在等待 I/O(如数据库操作),那么线程将不会运行得更快。

    如果它在CPU中做很多计算,那么它是否有好处,取决于你如何编写它。

    【讨论】:

    • 真的吗?我的印象正好相反。高延迟操作(磁盘访问、网络调用、用户输入)应该是线程化的,这样你的 CPU 就不会闲置。同样,cpu 绑定操作已经在尽可能快地进行,因此将它们分开没有任何好处。当然,多核系统在一定程度上改变了游戏规则……但多核优化可能发生在比您或我工作的水平更低的水平上。
    • 是的,这就是您设计程序的方式,如果您可以在程序等待 I/O 时处理某些内容,那么会有一些好处,但是如果整个程序都需要并等待 I/O,那么它不会跑得更快
    【解决方案4】:

    当然不是。线程会产生开销,因此应用程序是否受益取决于parallel 的情况。

    【讨论】:

    • 谢谢。问题:您能否更具体地说明您的陈述。 “应用程序是否受益取决于它的并行程度”。
    • 就好像线程A和线程B有各自的工作要做,A不用等待B,反之亦然...
    【解决方案5】:

    不,不是。因为当你做多线程时,你的 CPU 必须在线程、内存、寄存器之间切换,而这会带来成本。有一些任务可以像归并排序一样整除,但是有些任务可能不能被子任务整除,比如检查一个数字是否是素数(这只是我突然的例子),然后如果你尝试将它分开出来,它就像一个单线程问题一样运行。

    【讨论】:

    • 这有什么问题?一台服务器能开1000万线程吗?即使线程是轻量级的,它仍然需要成本,对吧?
    【解决方案6】:

    在拥有数百个线程之前,上下文切换开销不是问题。上下文切换的问题经常被高估(运行任务管理器并通知已经启动了多少线程)。您观察到的峰值依赖于与本地 CPU 计算相比相当不稳定的网络通信。

    当系统由多个 (5-15) 个组件组成并且每个组件都有自己的消息队列和有界线程池时,我建议在 SEDA(分阶段事件驱动架构)中编写可扩展的应用程序。您可以调整池的大小,甚至应用更改线程池大小的算法,以使某些组件比其他组件更有效率(因为所有组件共享相同的 CPU)。您可以调整特定硬件的池大小,使 SEDA 应用程序非常可调。

    【讨论】:

      【解决方案7】:

      我见过真实世界的例子,其中代码在添加更多处理器(线程之间可怕的锁争用)时表现如此糟糕,以至于系统需要移除处理器才能恢复性能;所以是的,可以通过添加更多执行线程来使代码工作得更糟。

      IO 受限的应用是另一个很好的例子,如上所述。

      【讨论】:

        【解决方案8】:

        根据Amdahl's law,最大加速取决于可以并行化的算法的比例。如果算法是高度并行的,那么增加 CPU 和线程的数量将会有很大的增加。如果算法不是并行的(有很多代码流控制或数据争用),则没有任何收益,甚至可能发生性能下降。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-30
          • 2010-10-03
          • 2018-09-27
          • 1970-01-01
          • 2012-07-26
          • 2014-11-08
          相关资源
          最近更新 更多