0

我正在尝试 Java 中的 executor 服务,并编写了以下代码来运行 Fibonacci(是的,大规模递归版本,只是为了强调 executor 服务)。

令人惊讶的是,如果我将 nThreads 设置为 1,它会运行得更快。这可能与提交给执行器服务的每个“任务”的大小非常小有关。但如果我将 nThreads 设置为 1,它仍然必须是相同的数字。

为了查看对共享原子变量的访问是否会导致此问题,我用注释“查看文本”注释掉了这三行,并查看了系统监视器以查看执行需要多长时间。但结果是一样的。

知道为什么会这样吗?

顺便说一句,我想将它与 Fork/Join 的类似实现进行比较。事实证明它比 F/J 实现要慢得多。

public class MainSimpler {
    static int N=35;
    static AtomicInteger result = new AtomicInteger(0), pendingTasks = new AtomicInteger(1);
    static ExecutorService executor;

    public static void main(String[] args) {
        int nThreads=2;
        System.out.println("Number of threads = "+nThreads);
        executor = Executors.newFixedThreadPool(nThreads);
        Executable.inQueue = new AtomicInteger(nThreads);
        long before = System.currentTimeMillis();
        System.out.println("Fibonacci "+N+" is ... ");
        executor.submit(new FibSimpler(N));
        waitToFinish();
        System.out.println(result.get());
        long after = System.currentTimeMillis();        
        System.out.println("Duration: " + (after - before) + " milliseconds\n");
    }

    private static void waitToFinish() {
        while (0 < pendingTasks.get()){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executor.shutdown();
    }
}



class FibSimpler implements Runnable {
    int N;
    FibSimpler (int n) { N=n; }

    @Override
    public void run() {
        compute();
        MainSimpler.pendingTasks.decrementAndGet(); // see text
    }

    void compute() {
        int n = N;
        if (n <= 1) {
            MainSimpler.result.addAndGet(n); // see text
            return;
        }
        MainSimpler.executor.submit(new FibSimpler(n-1));
        MainSimpler.pendingTasks.incrementAndGet(); // see text
        N = n-2;
        compute();  // similar to the F/J counterpart
    }
}

运行时间(大约):

  • 1 个线程:11 秒
  • 2 个线程:19 秒
  • 4 个线程:19 秒

更新:我注意到即使我在执行器服务中使用一个线程,整个程序也会使用我机器的所有四个内核(每个内核平均使用率约为 80%)。这可以解释为什么在 executor 服务中使用更多线程会减慢整个进程,但是现在,如果 executor 服务中只有一个线程处于活动状态,为什么这个程序使用 4 个内核?

4

1 回答 1

2

这可能与提交给执行器服务的每个“任务”的大小确实很小有关。

情况确实如此,因此您主要测量上下文切换的开销。当 n == 1 时,没有上下文切换,因此性能更好。

但如果我将 nThreads 设置为 1,它仍然必须是相同的数字。

我猜你的意思是“高于 1”。

您遇到了严重的锁争用问题。当你有多个线程时,锁上的锁一直在result竞争。线程必须相互等待才能更新它们result,这会减慢它们的速度。当只有一个线程时,JVM 可能会检测到并执行锁省略,这意味着它实际上根本不执行任何锁定。

如果您不将问题划分为N任务,而是将其划分为N/nThreads可以由线程同时处理的任务(假设您选择nThreads最多可用的物理内核/线程的数量),您可能会获得更好的性能。然后每个线程做自己的工作,计算自己的总数,并仅在线程完成时将其添加到总计中。即便如此,因为fib(35)我预计线程管理的成本将超过收益。也许试试fib(1000)

于 2012-11-30T11:31:33.157 回答