zoukankan      html  css  js  c++  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);
    }

    上面是一个抽象类,大致就是定义了几种求解素数的工具,求一个范围的素数,还有计时,还有求解整个范围素数的方法,而这个方法是抽象的也是我们需要实现的,也是并行和非并行的不同之处.

    顺序执行的方法:

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

    很简单,已经有现成的工具了嘛,直接用就好.

    并行的方法:

    并行的精髓是做任务分割,具体怎么分割上面其实已经表现出来了,讲整个求解范围进行分割,然后把求解结果累加即可.下面是代码

    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);
            
        }

    现在我看看下运行对比

    小于3000000的素数的个数为216816花费的时间6.464452757
    小于3000000的素数的个数为216816花费的时间12.226215544

    可以看到并行的时间是顺序执行的一半,虽然代码上复杂了很多,但是效果还是比较明显的,这里就涉及到了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.在子任务数超过一定的数量后,再继续增加的话,对性能提升是十分有限的

  • 相关阅读:
    springboot
    POI/JFreeChart
    ssm(6)spring-test
    DBUtils与BeanUtils
    数据连接池C3P0/DBCP/DRUID/自定义连接池
    web核心(3)响应头请求头状态码及dns解析过程
    log4j/Logback/SLF4j
    ssm(4)整合
    列表字典元组方法
    第四天 Python基础语法 编码规范 变量
  • 原文地址:https://www.cnblogs.com/color-my-life/p/4340912.html
Copyright © 2011-2022 走看看