有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

与单线程相比,奇怪的c++/java多线程性能结果

从2天开始我就在努力理解C++线程池的性能,而我决定在java上做同样的事情,这是我注意到C++和java的行为是一样的。基本上我的代码很简单

package com.examples.threading
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadPool {

final static AtomicLong lookups = new AtomicLong(0);
final static AtomicLong totalTime = new AtomicLong(0);

public static class Task implements Runnable 
{
    int start = 0;
    Task(int s) {
        start = s;
    }

    @Override
    public void run() 
    {
       for (int j = start ; j < start + 3000; j++ ) {
        long st = System.nanoTime();
        boolean a = false;
        long et = System.nanoTime();
        totalTime.getAndAdd((et - st));
        lookups.getAndAdd(1l);
       }
    }
}


 public static void main(String[] args) 
{
    // change threads from 1 -> 100 then you will get different numbers
    ExecutorService executor = Executors.newFixedThreadPool(1);
    for (int i = 0; i <= 1000000; i++) 
    {
        if (i % 3000 == 0) {
            Task task = new Task(i);
            executor.execute(task);
            System.out.println("in time " + (totalTime.doubleValue()/lookups.doubleValue()) + " lookups: " + lookups.toString());
        }
    }
    executor.shutdown();
    while (!executor.isTerminated()) {
        ;
    }
    System.out.println("in time " + (totalTime.doubleValue()/lookups.doubleValue()) + " lookups: " + lookups.toString());
}

}

现在,当您使用不同的池号运行相同的代码时,例如100个线程,总运行时间将发生变化

one thread:
in time 36.91493612774451 lookups: 1002000

100 threads: in time 141.47934530938124 lookups: 1002000

问题是,代码是一样的,为什么总运行时间不同,这里到底发生了什么


共 (1) 个答案

  1. # 1 楼答案

    这里有几个显而易见的可能性

    一个是System.nanoTime可以在内部序列化,因此即使每个线程单独进行调用,它也可以在内部按顺序执行这些调用(例如,在调用进入时排队)。当nanoTime直接访问硬件时钟时,尤其可能出现这种情况,例如在Windows上(它使用Windows QueryPerformanceCounter

    另一个基本上是顺序执行的点是原子变量。即使您使用的是无锁原子,但基本事实是每个原子都必须以原子序列的形式执行读/修改/写操作。对于锁定的变量,这是通过锁定、读取、修改、写入和解锁来完成的。使用无锁,您可以消除执行此操作时的一些开销,但您仍然无法摆脱这样一个事实:在给定的时间内,只有一个线程可以成功地读取、修改和写入特定的内存位置

    在这种情况下,每个线程所做的唯一“工作”是微不足道的,而且结果永远不会被使用,因此优化器可以(而且可能会)完全消除它。所以你真正测量的是读取时钟和增加变量的时间

    为了至少获得一些速度恢复,您可以(例如)给线程自己的lookupstotalTime变量。然后,当所有线程完成时,可以将各个线程的值相加,得到每个线程的总值

    防止时序的序列化稍微困难一点(说得委婉一点)。至少在显而易见的设计中,对nanoTime的每次调用都直接访问硬件寄存器,而硬件寄存器(至少对于大多数典型硬件)只能按顺序进行。它可以在硬件级别进行修复(提供一个高频定时器寄存器,每个核心可以直接读取,保证在核心之间同步)。这是一项非常重要的任务,而且(更重要的是)大多数当前的硬件都不包括这样的东西

    除此之外,在每个线程中做一些有意义的工作,这样当你在多个线程中执行时,你就有了一个东西,它实际上可以使用多个CPU/内核的资源来运行得更快