大规模素数的求解为题

问题原型:求三千万以内的素数.

问题的解决办法很简单,写一个求素数算法 然后循环就可以.这个是顺序执行方式,下面附上代码

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.在子任务数超过一定的数量后,再继续增加的话,对性能提升是十分有限的

时间: 2024-08-03 06:33:33

大规模素数的求解为题的相关文章

素数的求解(c语言)

输入100到200之间所有的素数. (1)通过两层for循环实现 #include <stdio.h> int main() { int i; int j; for (i=100;i<=200;i++) { for (j=2;j<=i-1;j++) { if(i%j==0) break; } if(j==i) printf("%d ",i); } return 0; } 优化: #include <stdio.h> int main() { int

杭电(hdu)ACM 4548 美素数

美素数 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 4482    Accepted Submission(s): 1524 Problem Description 小明对数的研究比較热爱,一谈到数.脑子里就涌现出好多数的问题,今天.小明想考考你对素数的认识. 问题是这种:一个十进制数.假设是素数,并且它的各位数字和也是素数,则称之

素数的筛法

一 写在开头 1.1 本文内容 本文实现了素数的筛法算法. 二 算法原理与实现 在写代码的过程中,时不时会遇到求解素数的任务,特意将素数求解方法总结成文章以备不时之需.素数的求解算法大概有两种.一种是枚举某一范围的数,然后逐个判断该数是否为素数.这种方法简单但效率不高.另一种方法是使用素数的筛法将某一范围内的所有素数筛选出来,然后再打表.筛法的原理很简单:从最小的素数2开始,依次将2的倍数给剔除,然后将后面没有被剔除的最小素数3的倍数依次剔除......重复上述操作就可以将范围内的所有合数给剔除

POJ 1166 The Clocks (爆搜 || 高斯消元)

题目链接 题意: 输入提供9个钟表的位置(钟表的位置只能是0点.3点.6点.9点,分别用0.1.2.3)表示.而题目又提供了9的步骤表示可以用来调正钟的位置,例如1 ABDE表示此步可以在第一.二.四.五个钟调正,如原来是0点,那么调正后为3点.问经过那些步骤可以导致9个钟的位置都在0点. 分析: 这个本来是一个高斯消元的题目,但是 听说周期4不是素数, 求解过程中不能进行取余.因为取余可能导致解集变大. 不过也有用高斯消元做的,下面是用高斯消元的分析 ” Discuss也有人讨论了,4不是质数

笔试算法题(20):寻找丑数 &amp; 打印1到N位的所有的数

出题:将只包含2,3,5的因子的数称为丑数(Ugly Number),要求找到前面1500个丑数: 分析: 解法1:依次判断从1开始的每一个整数,2,3,5是因子则整数必须可以被他们其中的一个整除,如果不包含任何其他因子则最终的结果为1: 解法2:小丑数必然是某个大丑数的因子,也就是乘以2,3,或者5之后的值,所以可以利用已经找到的丑数来寻找下一个丑数,使用数组有序保存已经找到的丑 数,并且当前最大丑数值为M:用大于M/2的丑数乘以2得到M1,用大于M/3的丑数乘以3得到M2,用大于M/5的丑数

POJ 2480 求每一个数对于n的最大公约数的和

这里是枚举每一个最大公约数p,那么最后求的是f(n) = sigma(p*phi(n/p))    phi()为欧拉函数 这里可以试着算一下,然后会发现这个是积性函数的 那么只要考虑每一类质数分开算,最后乘在一起就行了 而对于f(p^k) p为素数的求解可以这样考虑 对于前一个f(p^(k-1)) , 那么f(p^k)相当于把f(p^(k-1)) 中的所有情况都乘上了p ,  然后加上新产生的gcd()=1的情况,这个利用过程中的欧拉函数定理求解 phi(n) = (p1-1)*p1^(k1-1

ACM程序设计学期总结

心路历程         大一的时候就跟着费老师学了近一个学期的ACM程序设计课程,但是由于种种出自于自己的原因,当时并没有完全塌下心去学,只是学到了些基础的东西,做的题太少,当然也并没有深入理解.大二又有了这个机会,自然不能放过,又认认真真重新学了一遍.         虽然这学期这门课一共只学了四个专题,但是对我来说,收获非常大,正像费老师说的:acm是思维的体操.它不停留在基本的语言层面,而是专注于解决各种实际问题的有一定难度的算法,它虽然会用到各种思想.各种工具,但是它不拘泥于某种固定的

三 数论的编程实验

算法竞赛中围绕数论三个重要问题 素数运算 求解不定方程和同余房程 积性函数的应用 1.素数运算实验范例 1).计算[2,n]区间中所有素数 2) 大整数素数测试 1.1 使用筛法生成[2,n]区间中所有素数 a. 最简单之埃拉托斯特尼筛法 思想:辅助数组做筛子,递增搜索筛子中最小数,把被搜索数的倍数从筛中去掉,最后留下的数字是素数 实现代码: #include <iostream> #define MAXN 50005 using namespace std; bool u[MAXN]; //

Math-summary

1.Combinatorial Mathematics 1.1 Bell Number: \(B_n\)表示元素个数为n的集合划分成若干个不相交集合的方案数 \(B_{n + 1} = \sum_{k = 0}^n C(n,k)B_k\). 1.2 Catalan Number: 递推公式: \(h_1 = 1, h_n = \frac{h_{n-1}(4n-2)}{n+1}\). 组合数公式:\(h_n = \frac{C(2n,2)}{n +1} = C(2n,n) - C(2n,n+1)\