多线程总是比单线程产生更好的性能吗?

时间:2014-12-05 15:37:15

标签: java multithreading

我知道答案是,这是一个示例Why single thread is faster than multithreading in Java?

因此,当在线程中处理任务很简单时,创建线程的成本将比分发任务产生更多的开销。这是一个单线程比多线程更快的情况。

问题

  • 是否有更多情况下单个线程比多线程更快?

  • 我们什么时候应该决定放弃多线程,只使用一个线程来实现我们的目标?

虽然问题标记为,但欢迎在Java之外进行讨论。 如果我们能在答案中有一个小例子来解释,那就太好了。

7 个答案:

答案 0 :(得分:8)

这是一个关于线程及其与实际工作的链接的非常好的问题,这意味着可用的物理CPU及其内核和超线程。

  1. 如果您的CPU有多个可用核心,则多个线程可能允许您并行执行操作。所以在一个理想的世界中,例如如果你的CPU有4个可用内核并且你的算法非常平行,那么使用4个线程计算一些质数可能会快4倍。
  2. 如果在核心可用时启动更多线程,则操作系统的线程管理将花费越来越多的时间在线程交换机中,因此使用CPU的效率会变差。
  3. 如果编译器,CPU缓存和/或运行时意识到您运行多个线程,访问内存中的相同数据区域,则以不同的优化模式运行:只要编译/运行时确定只有一个线程访问数据,可以避免过于频繁地将数据写入外部RAM,并可能有效地使用CPU的L1缓存。如果不是:必须激活信号量,并且还要更频繁地将缓存数据从L1 / L2缓存刷新到RAM。
  4. 所以从高度平行的多线程中学到的经验是:

    • 如果可能,使用单线程,无共享流程来提高效率
    • 如果需要线程,请尽可能地分离共享数据访问
    • 如果可能,请不要尝试分配比可用核心更多的加载工作线程

    这里有一个小程序(javafx)可以玩。它:

    • 分配一个100.000.000大小的字节数组,填充随机字节
    • 提供一种方法,计算此数组中设置的位数
    • 该方法允许计算每个'第n个'字节位
    • count(0,1)将计算所有字节位
    • count(0,4)将计数0',4',8'字节位,允许并行交错计数

    使用MacPro(4核)导致:

    1. 运行一个线程,count(0,1)需要1326ms来计算所有399993625位
    2. 并行运行两个线程,count(0,2)和count(1,2)需要920ms
    3. 运行四个线程,需要618ms
    4. 运行8个线程,需要631ms
    5. enter image description here enter image description here enter image description here enter image description here

      更改计数方式,例如递增共同的整数(AtomicInteger或synchronized)将极大地改变许多线程的性能。

      public class MulithreadingEffects extends Application {
          static class ParallelProgressBar extends ProgressBar {
              AtomicInteger myDoneCount = new AtomicInteger();
              int           myTotalCount;
              Timeline      myWhatcher = new Timeline(new KeyFrame(Duration.millis(10), e -> update()));
              BooleanProperty running = new SimpleBooleanProperty(false);
      
              public void update() {
                  setProgress(1.0*myDoneCount.get()/myTotalCount);
                  if (myDoneCount.get() >= myTotalCount) {
                      myWhatcher.stop();
                      myTotalCount = 0;
                      running.set(false);
                  }
              }
      
              public boolean isRunning() { return myTotalCount > 0; }
              public BooleanProperty runningProperty() { return running; }
      
              public void start(int totalCount) {
                  myDoneCount.set(0);
                  myTotalCount = totalCount;
                  setProgress(0.0);
                  myWhatcher.setCycleCount(Timeline.INDEFINITE);
                  myWhatcher.play();
                  running.set(true);
              }
      
              public void add(int n) {
                  myDoneCount.addAndGet(n);
              }
          }
      
          int mySize = 100000000;
          byte[] inData = new byte[mySize];
          ParallelProgressBar globalProgressBar = new ParallelProgressBar();
          BooleanProperty iamReady = new SimpleBooleanProperty(false);
          AtomicInteger myCounter = new AtomicInteger(0);
      
          void count(int start, int step) {
              new Thread(""+start){
                  public void run() {
                      int count = 0;
                      int loops = 0;
                      for (int i = start; i < mySize; i+=step) {
                          for (int m = 0x80; m > 0; m >>=1) {
                              if ((inData[i] & m) > 0) count++;
                          }
                          if (loops++ > 99) {
                              globalProgressBar.add(loops);
                              loops = 0;
                          }
                      }
                      myCounter.addAndGet(count);
                      globalProgressBar.add(loops);
                  }
              }.start();
          }
      
          void pcount(Label result, int n) {
              result.setText("("+n+")");
              globalProgressBar.start(mySize);
              long start = System.currentTimeMillis();
              myCounter.set(0);
              globalProgressBar.runningProperty().addListener((p,o,v) -> {
                  if (!v) {
                      long ms = System.currentTimeMillis()-start;
                      result.setText(""+ms+" ms ("+myCounter.get()+")");
                  }
              });
              for (int t = 0; t < n; t++) count(t, n);
          }
      
          void testParallel(VBox box) {
              HBox hbox = new HBox();
      
              Label result = new Label("-");
              for (int i : new int[]{1, 2, 4, 8}) {
                  Button run = new Button(""+i);
                  run.setOnAction( e -> {
                      if (globalProgressBar.isRunning()) return;
                      pcount(result, i);
                  });
                  hbox.getChildren().add(run);
              }
      
              hbox.getChildren().addAll(result);
              box.getChildren().addAll(globalProgressBar, hbox);
          }
      
      
          @Override
          public void start(Stage primaryStage) throws Exception {        
              primaryStage.setTitle("ProgressBar's");
      
              globalProgressBar.start(mySize);
              new Thread("Prepare"){
                  public void run() {
                      iamReady.set(false);
                      Random random = new Random();
                      random.setSeed(4711);
                      for (int i = 0; i < mySize; i++) {
                          inData[i] = (byte)random.nextInt(256);
                          globalProgressBar.add(1);
                      }
                      iamReady.set(true);
                  }
              }.start();
      
              VBox box = new VBox();
              Scene scene = new Scene(box,400,80,Color.WHITE);
              primaryStage.setScene(scene);
      
              testParallel(box);
              GUIHelper.allowImageDrag(box);
      
              primaryStage.show();   
          }
      
          public static void main(String[] args) { launch(args); }
      }
      

答案 1 :(得分:5)

并非所有算法都可以并行处理(严格顺序的算法; Amdahl's law中P = 0)或至少不能有效处理(参见P-complete)。其他算法更适合并行执行(极端情况称为 "embarrassingly parallel" )。

与类似的顺序算法相比,并行算法的简单实现在复杂性或空间方面可能效率较低。如果没有明显的方法来并行化算法以使其获得加速,那么您可能需要选择另一个类似的并行算法来解决相同的问题但可能效率更高或更低。如果忽略线程/进程创建和直接的进程间通信开销,则在使用IO瓶颈等共享资源或内存消耗增加导致的页面分页增加时,仍可能存在其他限制因素。

  

我们什么时候应该决定放弃多线程,只使用一个线程来实现我们的目标?

在单线程和多线程之间进行决策时,应考虑更改实现所需的时间以及开发人员增加的复杂性。如果使用多个线程只有很小的收益,你可能会认为通常由多线程应用程序引起的维护成本增加不值得加速。

答案 2 :(得分:3)

开销不仅可以用于创建,还可以用于线程互通。另一件应该注意的是,例如单个对象上的线程同步可能导致单线程执行相似。

答案 3 :(得分:3)

线程是利用空闲资源来处理更多工作。如果没有空闲资源,多线程没有优势,因此开销实际上会使整个运行时间更长。

例如,如果要执行任务集合,则它们是CPU密集型计算。如果你有一个CPU,多线程可能不会加快这个过程(虽然你直到测试才知道)。我希望它会略微放缓。您正在改变工作的分配方式,但容量没有变化。如果您在一个CPU上有4个任务要做,那么连续执行1 * 4。如果你并行执行它们,你将基本上4 * 1出来,这是相同的。另外,合并结果和上下文切换的开销。

现在,如果您有多个CPU,那么在多个线程中运行CPU密集型任务将允许您使用未使用的资源,因此每单位时间可以完成更多操作。

另外,请考虑其他资源。如果您有4个查询数据库的任务,并行运行它们会有助于数据库有额外的资源来处理它们。但是,您还要添加更多工作,从数据库服务器中删除资源,因此我可能不会这样做。

现在,假设我们需要对3个外部系统进行Web服务调用,并且所有调用都没有相互依赖的输入。与多个线程并行执行它们意味着我们不必在另一个线程开始之前等待一个结束。这也意味着并行运行它们不会对每项任务产生负面影响。这将是多线程的一个很好的用例。

答案 4 :(得分:3)

正如@Jim Mischel在评论中提到的那样,你可以使用

Amdahl's law

计算这个。 Amdahl定律指出,通过添加处理器来解决任务所获得的加速是

enter image description here

,其中

N 是处理器的数量,

P 是可以并行执行的代码的一部分(0 ... 1)

现在,如果 T 是在单个处理器上执行任务所需的时间, O 是总的“开销”时间(创建并设置第二个)线程,通信,...),单个线程如果

更快

T&lt; T / S(2)+ O

或重新排序后,如果

O / T&gt; P / 2

当比率开销/执行时间大于 P / 2 时,单线程更快。

答案 5 :(得分:2)

  

是否有更多情况下单个线程比多线程更快?

因此,在GUI应用程序中,您将受益于多线程。在最基本的层面上,您将更新前端以及前端呈现的内容。如果你正在运行像hello world这样基本的东西,那么就像你展示的那样会增加开销。

这个问题非常广泛......您是否将单元测试视为应用程序?如果是这样,那么可能有更多的应用程序使用单线程,因为任何复杂的系统都有(希望)至少有1个单元测试。您是否将每个Hello世界风格的程序视为不同的应用程序或相同?如果某个应用程序被删除,它仍然会被计算?

正如你所看到的,除了你必须缩小问题范围以获得有意义的答案之外,我无法做出好的回应。话虽如此,这可能是我不知道的统计数据。

  

我们什么时候应该决定放弃多线程,只使用一个线程来实现我们的目标?

当多线程通过您认为重要的任何指标执行“更好”时。

您的问题可以分解为可以同时处理的部分吗?不是以一种人为的方式将Hello World分成两个线程,其中一个线程在另一个线程上等待打印。但是在某种程度上,2 +线程能够比一个线程更有效地完成任务?

即使任务很容易并行化也不意味着它应该是。我可以多线程一个应用程序,不断地控制数以千计的新网站来获取我的新闻。对我个人而言,这会很糟糕,因为它会占用我的烟斗而我无法获得我的FPS。对于CNN而言,这可能正是他们想要的,并将构建一个大型机来容纳它。

你可以缩小你的问题吗?

答案 6 :(得分:0)

这里可能发生两种情况:

  1. 单核CPU上的多线程:

    1.1 何时使用:当需要并行处理的任务受IO约束时,多线程处理将有所帮助。线程在等待IO和OS将时间片分配给其他等待线程时放弃执行。顺序执行没有这种行为-多线程将提高性能。

    1.2 不使用时:当任务不受IO限制并且仅是某种计算时,您可能不希望进行多线程处理,因为如果发生以下情况,线程创建和上下文切换将抵消收益任何。 -多线程的影响最小。

  2. 多核CPU中的多线程:多核可以运行的线程数与CPU中的核数一样多。这肯定会提高性能。但是,运行比可用内核更多的线程数将再次引入线程上下文切换问题。 -多线程肯定会产生影响。

注意:另外,在系统中添加/引入线程数也有限制。更多的上下文切换将抵消整体收益,并且降低应用速度。