大規模素數的求解爲題

問題原型:求三千萬之內的素數.java

問題的解決辦法很簡單,寫一個求素數算法 而後循環就能夠.這個是順序執行方式,下面附上代碼算法

public abstract class PrimeAbstract {
    public boolean isPrime(int i){
        if(i<=1)return false;
        else{
            for(int j=2; j<=Math.sqrt(i);j++){
                if(i%j == 0)return false;
            }
            return true;
        }
    }
    
    public static void print(Object a){
        System.out.println(a);
    }
    
    public int countPrimeInRange(int low, int up){
        int total  = 0;
        for(int i=low; i<=up; i++){
            if(isPrime(i))total++;
        }
        return total;
    }
    
    public void timeAndCompute(final int number){
        final long start  =System.nanoTime();
        final long num = countPrimes(number);
        final long end = System.nanoTime();
        print("小於"+number+"的素數的個數爲"+num+"花費的時間"+(end-start)/1.0e9);
    }
    
    public abstract int countPrimes(final int max);
}

上面是一個抽象類,大體就是定義了幾種求解素數的工具,求一個範圍的素數,還有計時,還有求解整個範圍素數的方法,而這個方法是抽象的也是咱們須要實現的,也是並行和非並行的不一樣之處.負載均衡

順序執行的方法:ide

class primeshunxu extends PrimeAbstract{

    @Override
    public int countPrimes(int max) {
        // TODO Auto-generated method stub
        return countPrimeInRange(0, max);
    }
    
}

很簡單,已經有現成的工具了嘛,直接用就好.工具

並行的方法:性能

並行的精髓是作任務分割,具體怎麼分割上面其實已經表現出來了,講整個求解範圍進行分割,而後把求解結果累加便可.下面是代碼spa

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class Prime extends PrimeAbstract{
    

    @Override
    public int countPrimes(int max) {
        // TODO Auto-generated method stub
        final int poolsize = (int) (Runtime.getRuntime().availableProcessors());
        final ExecutorService executorpool = Executors.newFixedThreadPool(poolsize);
        final List<Callable<Integer>> partition = new ArrayList<>();
        int total = 0;
        int tasks  =poolsize;
        int per = max/tasks;
        int pre = 0;
        for(int j = 1; j<=tasks; j++){
            final int low = pre+1;
            final int up;
            if(low+per>max){
                up = max;
            }else{
                up = low+per;
            }
            partition.add(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    // TODO Auto-generated method stub
                    return countPrimeInRange(low, up);
                }
            });
            pre = up;
        }
        try {
            List<Future<Integer>> result = executorpool.invokeAll(partition);
            executorpool.shutdown();
            for(Future<Integer> i : result){
                total += i.get();
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return total;
    }

}

最後調用線程

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int max = 3000000;
        new Prime().timeAndCompute(max);
        new primeshunxu().timeAndCompute(max);
        
    }

如今我看看下運行對比code

小於3000000的素數的個數爲216816花費的時間6.464452757
小於3000000的素數的個數爲216816花費的時間12.226215544blog

能夠看到並行的時間是順序執行的一半,雖然代碼上覆雜了不少,可是效果仍是比較明顯的,這裏就涉及到了poolsize和tasks的個數問題:

根據poolsize計算公式,對於計算密集型任務,阻塞係數是0,那麼poolsize大小設置成和cpu核數相等便可,由於假如設置大了之後,cpu就會掛起一個非阻塞線程,而後去執行另外一個非阻塞任務,這樣作意義不大,並且會代碼額外的上下文切換開銷.也就是在cpu密集型任務中,經過將線程數增多到比核數還多,是無效的.

那麼剩下的就是tasks了.

咱們首先看tasks = poolsize的狀況,也就是將每一個線程執行一個任務,而後就結束

看下分析結果

執行輸出

小於3000000的素數的個數爲216816花費的時間6.24529681

能夠看到 有四個線程,由於本機的核數爲4,而後其中三個線程在運行完畢後進入了駐留狀態,而還有一個線程從開始到最後一直在執行,也就是那個數值分配最大的那個線程,因爲計算花費的時間更多,因此最好只有這單個線程執行了.因此運行時間也就是運行時間最長的那個線程所花的時間

那麼咱們能夠想到,這樣來分配任務的話,形成了四個線程的負載時不均衡的,計算量最大的線程是時間瓶頸,那麼咱們能夠怎麼作呢?

1.在任務數不變的狀況下從新劃分,能夠想象前面12.5%的數計算量很小,後12.5%計算量很大,這前面的八分之一和後面的八分之一組合成一個任務,這樣來進行均衡,可是這樣作要對問題自己的行爲要有一個很是深入的理解,實現相對麻煩

2.增長任務數,咱們能夠想象,當咱們任務數變多的時候,每次四個線程上跑的都是時間花銷差很少的任務,這樣一來,咱們就可以作到任務均衡了.可是具體增長到多少呢?

咱們增長到100個任務

運行結果:

小於3000000的素數的個數爲216816花費的時間5.921416693

能夠看到,有了一些提高,並且每一個線程從開始到最後都是滿負載的,作到了負載均衡.這裏雖然沒有理論上的達到順序執行的四分之一,也就是四秒左右,可是已經很客觀了.

 

關於poolsize和tasks的總結:

1.子任務的劃分數是不能小於處理器的核心數,不然性能都不好的.

2.對於計算密集型任務,線程數多於cpu核心數對性能提高是毫無幫助的.

3.在子任務數超過必定的數量後,再繼續增長的話,對性能提高是十分有限的

相關文章
相關標籤/搜索