我正在尝试Java中的执行程序服务,并编写了以下代码来运行Fibonacci(是的,大规模递归版本,只是为了强调执行程序服务)。
令人惊讶的是,如果我将nThreads设置为1,它将运行得更快。这可能与提交给执行程序服务的每个“任务”的大小非常小有关。但是如果我将nThreads设置为1,它仍然必须是相同的数字。
要查看对共享原子变量的访问是否会导致此问题,我用注释“see text”注释掉了三行,并查看了系统监视器以查看执行所需的时间。但结果是一样的。
知道为什么会这样吗?
顺便说一句,我想将它与使用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
}
}
运行时(大约):
更新: 我注意到即使我在执行程序服务中使用一个线程,整个程序也将使用我的机器的所有四个核心(每个核心平均使用80%左右)。这可以解释为什么在执行程序服务中使用更多线程会减慢整个过程的速度,但现在,如果执行程序服务中只有一个线程处于活动状态,为什么该程序使用4个核心?
答案 0 :(得分:2)
这可能与提交的每个“任务”的大小有关 执行者服务真的很小。
肯定是这种情况,因此您主要测量上下文切换的开销。当n == 1时,没有上下文切换,因此性能更好。
但如果我将nThreads设置为1,它仍然必须是相同的数字。
我猜你的意思是'高于1'。
您遇到了严重的锁定争用问题。当您有多个线程时,result
上的锁定始终存在争用。线程必须等待彼此才能更新result
并减慢它们的速度。当只有一个线程时,JVM可能会检测到并执行锁定省略,这意味着它根本不会执行任何锁定。
如果您不将问题划分为N
任务,而是将其划分为N/nThreads
任务,可以获得更好的性能,这些任务可由线程同时处理(假设您选择{nThreads
1}}最多可用的物理内核/线程数)。然后,每个线程都会自己完成工作,计算自己的总数,并且只在线程完成时将其添加到总计中。即便如此,对于fib(35)
我预计线程管理的成本将超过收益。也许试试fib(1000)
。