我正在尝试执行一个简单的计算(它调用Math.random()
10000000次)。令人惊讶的是,使用简单方法运行它比使用ExecutorService要快得多。
我已经在ExecutorService's surprising performance break-even point --- rules of thumb?阅读了另一个帖子并试图通过使用批次执行Callable
来回答问题,但性能仍然不好
如何根据当前代码改进性能?
import java.util.*;
import java.util.concurrent.*;
public class MainTest {
public static void main(String[]args) throws Exception {
new MainTest().start();;
}
final List<Worker> workermulti = new ArrayList<Worker>();
final List<Worker> workersingle = new ArrayList<Worker>();
final int count=10000000;
public void start() throws Exception {
int n=2;
workersingle.add(new Worker(1));
for (int i=0;i<n;i++) {
// worker will only do count/n job
workermulti.add(new Worker(n));
}
ExecutorService serviceSingle = Executors.newSingleThreadExecutor();
ExecutorService serviceMulti = Executors.newFixedThreadPool(n);
long s,e;
int tests=10;
List<Long> simple = new ArrayList<Long>();
List<Long> single = new ArrayList<Long>();
List<Long> multi = new ArrayList<Long>();
for (int i=0;i<tests;i++) {
// simple
s = System.currentTimeMillis();
simple();
e = System.currentTimeMillis();
simple.add(e-s);
// single thread
s = System.currentTimeMillis();
serviceSingle.invokeAll(workersingle); // single thread
e = System.currentTimeMillis();
single.add(e-s);
// multi thread
s = System.currentTimeMillis();
serviceMulti.invokeAll(workermulti);
e = System.currentTimeMillis();
multi.add(e-s);
}
long avgSimple=sum(simple)/tests;
long avgSingle=sum(single)/tests;
long avgMulti=sum(multi)/tests;
System.out.println("Average simple: "+avgSimple+" ms");
System.out.println("Average single thread: "+avgSingle+" ms");
System.out.println("Average multi thread: "+avgMulti+" ms");
serviceSingle.shutdown();
serviceMulti.shutdown();
}
long sum(List<Long> list) {
long sum=0;
for (long l : list) {
sum+=l;
}
return sum;
}
private void simple() {
for (int i=0;i<count;i++){
Math.random();
}
}
class Worker implements Callable<Void> {
int n;
public Worker(int n) {
this.n=n;
}
@Override
public Void call() throws Exception {
// divide count with n to perform batch execution
for (int i=0;i<(count/n);i++) {
Math.random();
}
return null;
}
}
}
此代码的输出
Average simple: 920 ms
Average single thread: 1034 ms
Average multi thread: 1393 ms
编辑:由于Math.random()是一个同步方法,性能受到影响..在为每个线程更改带有新Random对象的Math.random()之后,性能得到了提升
新代码的输出(在为每个线程替换Math.random()之后使用Random)
Average simple: 928 ms
Average single thread: 1046 ms
Average multi thread: 642 ms
答案 0 :(得分:12)
Math.random()已同步。同步的全部意义在于减慢速度,使它们不会发生碰撞。使用未同步的内容和/或为每个线程提供自己的对象,例如新的Random。
答案 1 :(得分:3)
你最好阅读另一个帖子的内容。那里有很多好的提示。
根据Math.random()契约,您的基准测试可能是最重要的问题,“此方法已正确同步以允许多个线程正确使用。但是,如果许多线程需要在以下位置生成伪随机数一个很好的速率,它可以减少每个线程的争用,以拥有自己的伪随机数生成器“
将其读作:方法已同步,因此只有一个线程可能同时有效地使用它。因此,您需要花费大量开销来分发任务,只是强制它们再次串行运行。
答案 2 :(得分:1)
当您使用多个线程时,您需要了解使用其他线程的开销。您还需要确定您的算法是否具有可以并行执行的工作。所以你需要有可以同时运行的工作,这个工作足够大,超出了使用多个线程的开销。
在这种情况下,最简单的解决方法是在每个线程中使用单独的Random。你遇到的问题是,作为一个微基准测试,你的循环实际上并没有做任何事情,JIT非常擅长丢弃不做任何事情的代码。解决方法是将随机结果相加并从call()
返回,因为这通常足以阻止JIT丢弃代码。
最后,如果你想要总结很多数字,你不需要保存它们并在以后加总。你可以随时总结它们。