是否有任何“阈值”可以证明多线程计算的合理性?

11 浏览
0 Comments

是否有任何“阈值”可以证明多线程计算的合理性?

基本上,今天我需要优化这段代码。它试图找到由某个函数为前一百万个起始数字产生的最长序列:

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("最长序列长度为 " + mostLen + " 对应的数字为 " + 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> list = new ArrayList>();
    for (int j = 2; j <= 1000000; j++) {
        MyCallable worker = new MyCallable();
        worker.setBean(new ValueBean(j, 0));
        Future f = executor.submit(worker);
        list.add(f);
    }
    System.out.println(System.currentTimeMillis() - currTime);
    int mostLen = 0;
    int mostInt = 0;
    for (Future 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("最长序列长度为 " + mostLen + " 对应的数字为 " + mostInt);
}
public class MyCallable implements Callable {
    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("最长序列长度为 " + mostLen + " 对应的数字为 " + 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
0 Comments

有人提出了一个问题,即在多线程计算中是否存在一个“阈值”,可以证明性能的提升是否大于上下文切换和线程创建的成本。这个问题的出现原因是因为上下文切换和线程创建的成本是与操作系统、编程语言和硬件有关的。在Java中,创建一个线程的成本是昂贵的,可以通过一些数字和计算方法来计算成本。另外,对于CPU密集型的任务,你通常希望每个CPU有一个线程或更少。你还需要一个线程来协调工作,所以一般是每个CPU有一个线程加上一个主线程。你可以通过一些方法来确定可用的CPU核心数量和其他有用的信息。

0
0 Comments

有没有证明多线程计算的"阈值"?

问题出现的原因是并行化在单位工作互不依赖的情况下效果最好。当后续计算结果依赖于先前计算结果时,并行计算就不是最优的选择。依赖关系可以是强依赖,即"我需要第一个值来计算第二个值"。在这种情况下,任务完全是串行的,后续值无法在等待先前计算结果的情况下进行计算。也可以存在较弱的依赖关系,即"如果我有第一个值,我可以更快地计算第二个值"。在这种情况下,并行化的成本是可能会重复一些工作。

这个问题可以通过优化而不使用多线程来解决,因为如果您已经有了先前的结果,一些后续值可以更快地计算出来。例如,考虑 j == 4。内部循环一次产生 i == 2,但是两次迭代前刚计算了 j == 2 的结果,如果保存了 len 的值,可以计算出 len(4) = 1 + len(2)。

使用数组来存储先前计算过的 len 值,并在 next 方法中进行一些修改,可以使任务完成速度提高超过50倍。

是的,这比批量多线程的1000倍还要快!我想知道是否可以将这个问题多线程化。

这是可能的。我会研究一下 ConcurrentHashMap,这样我就可以在构建缓存时不必担心锁问题。虽然我认为数组实现非常快,因为只要 i < j,就知道它在缓存中,但是哈希查找可能会慢很多。如果您可以利用 next 函数的其他数学属性,很容易证明对于一个限制 n,具有最长长度的 j 必须满足 j > n / 2。这有助于多线程解决方案,但不适用于缓存解决方案。此外,简单的数组缓存无法扩展到限制大于约 42,000,000 的情况。

0
0 Comments

多线程计算的效率与成本有着密切的关系,因此需要找到一个合适的阈值来确定是否使用多线程计算。没有固定的规则来确定这个阈值,因为它很大程度上取决于硬件。

启动和停止线程的成本很高。当然,你已经使用了执行器服务(executor service),它使用一组工作线程来执行你的Runnable,从而大大减少了这些成本。然而,每个Runnable仍然带有一些开销。减少Runnable的数量并增加每个Runnable需要执行的工作量将提高性能,但仍需要足够的Runnable来让执行器服务有效地将它们分配到工作线程上。

你选择为每个起始值创建一个Runnable,因此你最终会创建1000000个Runnable。如果让每个Runnable处理一批,比如1000个起始值,可能会得到更好的结果。这意味着你只需要1000个Runnable,大大减少了开销。

使用批处理的方式可以减少1000000个任务的开销,因为线程没有工作可做时会导致生产力下降。

0