您的位置:首页 > 编程语言 > Java开发

操作系统实验——串行、多线程和线程池三种方式计算矩阵乘法

2015-04-10 20:12 330 查看

操作系统实验——串行、多线程和线程池三种方式计算矩阵乘法

注:写实验报告的同学在复制代码的时候记得改一改变量名和函数!最好看懂了自己写一下!不要直接copy!太明显了~~

1.实验目的:以多线程(并行)、串行以及线程池三种形式计算矩阵乘法,统计分析三种方法所需要的时间,并根据公式speedup<=1/(s+(1-s)/n),计算出顺序执行占用比s.

2.对公式speedup<=1/(s+(1-s)/n)的解释:speedup=串行时间/并行时间,又称加速比,s是顺序执行占用比,也就是程序中必须串行(单线程)执行的部分,n是你的电脑的内核数,可通过设备管理器查看

3.实现思路:
    1)定义两个较大的1024阶方阵和确定线程数量n(n从2-1024*1024均可以)
    2)实现一个singleThread()函数,用来串行(直接)计算矩阵乘法
    3)实现一个工作线程workThread,每个线程完成矩阵乘法运算工作的1/n,创建n个线程,用多线程计算矩阵乘法
    4)声明一个线程池,将n个线程放入线程池中进行管理,用线程池进行矩阵乘法的运算

4.代码
工作线程类workThread:
package cn.edu.seu.yujun.OS;
/**
*
* @author Fish
* Date:2015/4/7
*/
public class WorkThread implements Runnable {
private int start;//计算开始位置,以此区分工作线程工作任务
private int [][]A;
private int [][]B;
private int [][]C;

//工作线程构造方法
public WorkThread(int start,int [][]A,int [][]B,int [][]C){
this.start=start;
this.A=A;
this.B=B;
this.C=C;
}

@Override
public void run() {
int i,j,k;
//根据线程数量划分每个工作线程任务
for(i=start; i<Driver.M; i +=Driver.NUM_THREADS)
{
for(j=0;j<Driver.N;j++)
{
for( k=0; k< Driver.K;k++)
C[i][j]+=A[i][k]*B[k][j];
}
}
}

}
驱动类Driver:
package cn.edu.seu.yujun.OS;
/**
* @author Fish
* Date:2015/4/7
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Driver {
public final static int M=1024;//定义常量:矩阵A的行数
public final static int K=1024;//定义常量:矩阵A的列数,矩阵B的行数
public final static int N=1024;//定义常量:矩阵B的列数
final static int NUM_THREADS=2;//定义常量:线程数量
private static int [][]A;//矩阵A
private static int [][]B;//矩阵B
private static int [][]C;//矩阵C
//---------------------
//驱动类构造方法
public Driver(){
A=new int[M][K];
B=new int[K]
;
C=new int[M]
;//A、B、C初始化
fillRandom(A);//用0-99的随机数初始化矩阵A
fillRandom(B);//用0-99的随机数初始化矩阵B
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
C[i][j]=0;//将C矩阵全置零
}

//-------------------
//初始化方法:产生0-99的随机数初始化矩阵A、B
private void fillRandom(int[][] A) {
for(int i=0;i<A.length;i++){
for(int j=0;j<A[i].length;j++)
A[i][j]=(int)(Math.random()*100);
}
}

//--------------------
//串行矩阵乘法运算
public static void singleThread(){
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
for(int k=0;k<K;k++)
C[i][j]+=A[i][k]*B[k][j];
}
}
}

//----------------------
//main函数
public static void main(String[] args){
new Driver();//新建一个驱动类对象

//随机产生三组C矩阵位置信息,便于后面验证三种方法计算结果是否都正确
int []rol=new int[3];
int []col=new int[3];
for(int i=0;i<rol.length;i++){
rol[i]=(int)(Math.random()*M);
col[i]=(int)(Math.random()*N);
}

//--------------------------------
//并行(4线程)方法:建立四个工作线程,每个线程完成矩阵乘法运算的1/4工作
Thread[] workers=new Thread[NUM_THREADS];
for(int i=0;i<NUM_THREADS;i++)
workers[i]=new Thread(new WorkThread(i,A,B,C));//建立四个工作线程
long time1= System.currentTimeMillis();//记录开始时间
for(int i=0;i<NUM_THREADS;i++){
workers[i].start();//启动四个工作线程
}
for(int i=0;i<NUM_THREADS;i++){
try{
workers[i].join();//等待当前线程执行结束
}catch(InterruptedException e){
e.printStackTrace();
}
}
long time2=System.currentTimeMillis();//记录结束时间
//打印方法一使用的时间和矩阵C三个随机位置的值
System.out.println("计算["+M+","+K+"]与["+K+","+N+"]阶矩阵乘法,并行("+NUM_THREADS+"线程)用时:"+(time2-time1)+"毫秒");
System.out.println(C[rol[0]][col[0]]+" "+C[rol[1]][col[1]]+" "+C[rol[2]][col[2]]);
System.out.println();

//---------------------------------
//方法二:串行也就是直接进行运算
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
C[i][j]=0;//将C矩阵全置零
long time3=System.currentTimeMillis();//记录开始时间
singleThread();//调用串行计算函数
long time4=System.currentTimeMillis();//记录结束时间
//打印方法二使用的时间和矩阵C三个随机位置的值
System.out.println("计算["+M+","+K+"]与["+K+","+N+"]阶矩阵乘法,直接计算用时:"+(time4-time3)+"毫秒");
System.out.println(C[rol[0]][col[0]]+" "+C[rol[1]][col[1]]+" "+C[rol[2]][col[2]]);
System.out.println();

//------------------------------
//方法三:使用线程池方法进行运算
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
C[i][j]=0;//将C矩阵全置零
//建立四个工作线程
Thread []poolThreads=new Thread[NUM_THREADS];
for(int i=0;i<NUM_THREADS;i++)
poolThreads[i]=new Thread(new WorkThread(i,A,B,C));
//建立线程池
ExecutorService pool = Executors.newCachedThreadPool();
long time5=System.currentTimeMillis();//记录开始时间
for(int i=0;i<NUM_THREADS;i++)
pool.execute(poolThreads[i]);//将四个工作线程放入线程池中执行
pool.shutdown();//在线程池终止前允许执行以前提交的任务
while (true) {
if (pool.isTerminated()) {
break;
}
}//用一个死循环判断线程池是否执行完成
long time6=System.currentTimeMillis();//记录结束时间
//打印方法二使用的时间和矩阵C三个随机位置的值
System.out.println("计算["+M+","+K+"]与["+K+","+N+"]阶矩阵乘法,线程池计算用时:"+(time6-time5)+"毫秒");
System.out.println(C[rol[0]][col[0]]+" "+C[rol[1]][col[1]]+" "+C[rol[2]][col[2]]);
}
}

5.运行结果:








根据上述结果可以看出:并不是线程数量越多,运算速度越快!运算时间与许多因素有关,创建线程也需要时间!在一些小型运算中,创建线程的时间甚至比计算时间还长;另外,曾测试过在程序中将for循环修改成顺序一条条语句,运算时间降低了很多!在不同时间进行计算结果也不太一致~~





随着线程数量增多,运算时间反而越来越长~~
另外,不同的电脑可能不太一样!大家可以自己测试一下~~

下面我们来计算一下s:
选取4线程时的数据,speedup=7058/4815=1.465,电脑内核数n是4,带入公式中可以得到s<=57.7%,也就是程序中必须单线程执行的占所有的比例小于57.7%
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息