所以基本上我今天需要优化这段代码。它试图找到由一些函数产生的最长序列,用于第一百万个起始数字:
public static void main(String[] args) {
int mostLen = 0;
int mostInt = 0;
long currTime = System.currentTimeMillis();
for(int j=2; j<=1000000; j++) {
long i = j;
int len = 0;
while((i=next(i)) != 1) {
len++;
}
if(len > mostLen) {
mostLen = len;
mostInt = j;
}
}
System.out.println(System.currentTimeMillis() - currTime);
System.out.println("Most len is " + mostLen + " for " + mostInt);
}
static long next(long i) {
if(i%2==0) {
return i/2;
} else {
return i*3+1;
}
}
我的错误是尝试引入多线程:
void doSearch() throws ExecutionException, InterruptedException {
final int numProc = Runtime.getRuntime().availableProcessors();
System.out.println("numProc = " + numProc);
ExecutorService executor = Executors.newFixedThreadPool(numProc);
long currTime = System.currentTimeMillis();
List<Future<ValueBean>> list = new ArrayList<Future<ValueBean>>();
for (int j = 2; j <= 1000000; j++) {
MyCallable<ValueBean> worker = new MyCallable<ValueBean>();
worker.setBean(new ValueBean(j, 0));
Future<ValueBean> f = executor.submit(worker);
list.add(f);
}
System.out.println(System.currentTimeMillis() - currTime);
int mostLen = 0;
int mostInt = 0;
for (Future<ValueBean> f : list) {
final int len = f.get().getLen();
if (len > mostLen) {
mostLen = len;
mostInt = f.get().getNum();
}
}
executor.shutdown();
System.out.println(System.currentTimeMillis() - currTime);
System.out.println("Most len is " + mostLen + " for " + mostInt);
}
public class MyCallable<T> implements Callable<ValueBean> {
public ValueBean bean;
public void setBean(ValueBean bean) {
this.bean = bean;
}
public ValueBean call() throws Exception {
long i = bean.getNum();
int len = 0;
while ((i = next(i)) != 1) {
len++;
}
return new ValueBean(bean.getNum(), len);
}
}
public class ValueBean {
int num;
int len;
public ValueBean(int num, int len) {
this.num = num;
this.len = len;
}
public int getNum() {
return num;
}
public int getLen() {
return len;
}
}
long next(long i) {
if (i % 2 == 0) {
return i / 2;
} else {
return i * 3 + 1;
}
}
不幸的是,多线程版本比4个处理器(内核)上的单线程工作慢5倍。
然后我尝试了更粗略的方法:
static int mostLen = 0;
static int mostInt = 0;
synchronized static void updateIfMore(int len, int intgr) {
if (len > mostLen) {
mostLen = len;
mostInt = intgr;
}
}
public static void main(String[] args) throws InterruptedException {
long currTime = System.currentTimeMillis();
final int numProc = Runtime.getRuntime().availableProcessors();
System.out.println("numProc = " + numProc);
ExecutorService executor = Executors.newFixedThreadPool(numProc);
for (int i = 2; i <= 1000000; i++) {
final int j = i;
executor.execute(new Runnable() {
public void run() {
long l = j;
int len = 0;
while ((l = next(l)) != 1) {
len++;
}
updateIfMore(len, j);
}
});
}
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
System.out.println(System.currentTimeMillis() - currTime);
System.out.println("Most len is " + mostLen + " for " + mostInt);
}
static long next(long i) {
if (i % 2 == 0) {
return i / 2;
} else {
return i * 3 + 1;
}
}
它运行得更快,但仍然比单线程方法慢。
我希望不是因为我搞砸了我正在进行多线程的方式,而是这种特殊的计算/算法不适合并行计算。如果我通过将方法next
替换为:
long next(long i) {
Random r = new Random();
for(int j=0; j<10; j++) {
r.nextLong();
}
if (i % 2 == 0) {
return i / 2;
} else {
return i * 3 + 1;
}
}
多线程版本的启动速度比4核机器上的单线程版本快两倍以上。
很明显,必须有一些阈值可用于确定是否值得引入多线程,我的问题是:
有哪些基本规则可以帮助确定给定的计算是否足够密集以通过并行运行来优化(不花费精力实际实现它?)
答案 0 :(得分:4)
有效实施多线程的关键是确保成本不会太高。没有固定的规则,因为它们严重依赖于您的硬件。
启动和停止线程的成本很高。当然,您已经使用了执行器服务,它可以大大降低这些成本,因为它使用一堆工作线程来执行Runnables。但是每个Runnable仍然会带来一些开销。减少可运行的数量并增加每个人必须做的工作量将提高性能,但是您仍然希望有足够的可运行执行服务,以便有效地将它们分配到工作线程上。
您已选择为每个起始值创建一个runnable,因此您最终会创建1000000个runnable。你可能会得到更好的结果让每个Runnable做一批1000个起始值。这意味着您只需要1000个runnable就可以大大减少开销。
答案 1 :(得分:2)
&#34;性能增益是否会高于上下文切换和线程创建的成本?&#34;
这是一个非常操作系统,语言和硬件,依赖成本; this question对Java的成本进行了一些讨论,但是有一些数字和一些指示如何计算成本。
对于CPU密集型工作,您还希望每个CPU或更少的一个线程。感谢David Harkness指针to a thread on how to work out that number。
答案 2 :(得分:2)
我认为还有另一个因素,你没有考虑。当工作单元彼此不依赖时,并行化最有效。当后面的计算结果取决于早期的计算结果时,并行运行计算是次优的。从“我需要第一个值来计算第二个值”的意义上来说,依赖性可能很强。在这种情况下,任务是完全串行的,并且在不等待早期计算的情况下无法计算以后的值。在“如果我有第一个值,我可以更快地计算第二个值”的意义上,也可能存在较弱的依赖性。在这种情况下,并行化的代价是某些工作可能会重复。
此问题有助于在没有多线程的情况下进行优化,因为如果您已经掌握了之前的结果,则可以更快地计算某些后续值。举个例子,j == 4
。一旦通过内部循环产生i == 2
,但你刚刚在两次迭代之前计算了j == 2
的结果,如果保存了len
的值,你可以将它计算为len(4)= 1 + len(2)。
使用数组存储先前计算的len
值并稍微调整next
方法,您可以更快地完成任务&gt; 50倍。
答案 3 :(得分:1)
估计线程可以在不与其他线程(直接或通过公共数据)交互的情况下完成的工作量。如果这项工作可以在1微秒或更短的时间内完成,则开销太大而且多线程是没有用的。如果它是1毫秒或更长,多线程应该运行良好。如果介于两者之间,则需要进行实验测试。