串行程序并行化

考虑这样一个问题:统计某个工程的代码行数。首先想到的思路便是,递归文件树,每层递归里,循环遍历父文件夹下的所有子文件,如果子文件是文件夹,那么再对这个文件夹进行递归调用。于是问题很轻松的解决了。这个方案可以优化吗? 了

再回想这个问题,可以发现,循环里的递归调用其实相互之间是独立的,互不干扰,各自统计自己路径下的代码文件的行数。于是,发现了这个方案的可优化点——利用线程池进行并行处理。于是一个串行的求解方案被改进成了并行方案。

不能光说不练,写了一个Demo,对串行方案和并行方案进行了量化对比。代码如下:

import java.io.*;
import java.util.Queue;
import java.util.concurrent.*;

/**
 * Created by cdlvsheng on 2016/5/16.
 */
public class ParallelSequentialContrast {

	int                    coreSize = Runtime.getRuntime().availableProcessors();
	ThreadPoolExecutor     exec     = new ThreadPoolExecutor(coreSize * 4, coreSize * 5, 0, TimeUnit.SECONDS,
			new LinkedBlockingQueue<Runnable>(10000), new ThreadPoolExecutor.CallerRunsPolicy());
	Queue<Future<Integer>> queue    = new ConcurrentLinkedQueue<Future<Integer>>();

	private int countLineNum(File f) {
		if (!f.getName().endsWith("java") && !f.getName().endsWith(".js") && !f.getName().endsWith(".vm")) return 0;

		int sum = 0;
		try {
			BufferedReader br  = new BufferedReader(new FileReader(f));
			String         str = null;
			while ((str = br.readLine()) != null) sum++;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return sum;
	}

	private class Task implements Callable<Integer> {
		File f;

		public Task(File f) {
			this.f = f;
		}

		public Integer call() throws Exception {
			int sum = 0;

			if (f.isDirectory()) {
				File[] fs = f.listFiles();
				for (File file : fs) {
					if (file.isDirectory()) queue.add(exec.submit(new Task(file)));
					else sum += countLineNum(file);
				}
			} else sum += countLineNum(f);

			return sum;
		}
	}

	public int parallelTraverse(File f) {
		queue.add(exec.submit(new Task(f)));
		int sum = 0;
		while (!queue.isEmpty()) {
			try {
				Future<Integer> future = queue.poll();
				sum += future.get();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		exec.shutdown();
		return sum;
	}

	public int sequentialTraverse(File f) {
		int sum = 0;

		if (f.isDirectory()) {
			File[] fs = f.listFiles();
			for (File file : fs) {
				if (file.isDirectory()) sum += sequentialTraverse(file);
				else sum += countLineNum(file);
			}
		} else sum += countLineNum(f);

		return sum;
	}

	public void parallelTest(ParallelSequentialContrast psc, String pathname) {
		long start    = System.currentTimeMillis();
		int  sum      = psc.parallelTraverse(new File(pathname));
		long duration = System.currentTimeMillis() - start;
		System.out.println(String.format("parallel test, %d lines of code were found, time cost is %d ms", sum, duration));
	}

	public void sequentialTest(ParallelSequentialContrast psc, String pathname) {
		long start    = System.currentTimeMillis();
		int  sum      = psc.sequentialTraverse(new File(pathname));
		long duration = System.currentTimeMillis() - start;
		System.out.println(String.format("sequential test, %d lines of code were found, time cost is %d ms", sum, duration));
	}

	public static void main(String[] args) {
		ParallelSequentialContrast psc      = new ParallelSequentialContrast();
		String                     pathname = "D:\\Code_Git";
		psc.sequentialTest(psc, pathname);
		psc.parallelTest(psc, pathname);
	}
}

因为要不断的扫磁盘(虽然我的是固态硬盘),所以并行方案的线程池开的很大。IO密集型程序的相对CPU密集型程序的线程池会更大。

程序运行结果如下:

sequential test, 415079 lines of code were found, time cost is 364 ms
parallel test, 415079 lines of code were found, time cost is 163 ms

可以发现,在结果同等精确的情况下,串行方案耗时是并行方案的两倍多。这个是在我个人PC上做的测试,如果是线上服务器运行,恐怕差距只会更加明显。

如果一个大任务,由许多个相互独立的子任务组成,我们就可以在这里找突破点,把一个串行程序并行化,榨干多和服务器的性能!

JDK1.7提供了一个Fork/Join的框架,其原理与这个并行方案如出一辙。Fork/Join框架在每fork一个任务后,都会把这个任务甩进一个工作队列,供线程池消费。所谓框架,也就是把常见问题的解决方案模板化,傻瓜化。关于Fork/Join框架,在我的前面一篇博客里有介绍:点击打开链接

时间: 2024-10-10 07:03:17

串行程序并行化的相关文章

串行(Sequential)、并发(Concurrent)、并行(parallel)与分布式

Table of Contents 1 串行(Sequential) 2 并发(Concurrent) 3 并行(parallel) 4 分布式(distributed) 5 <编译点滴>评 1 串行(Sequential) 串行程序中,程序会顺序的执行每一条指令,在整个程序的运行过程中,仅存在一个运行上下文.即一个调用栈,一个堆. 不存在多个运行上下文. 在传统的命令式语言中,如C/C++,Fortran,Java,C#之类,串行执行是根基. 2 并发(Concurrent) 并发是指,程序

Java对象的深复制----利用串行化进行深复制

把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序圈里又非常形象地称为“冷冻”或者“腌咸菜”过程:而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜”过程.应当指出的是,写到流里的是对象的一个拷贝,而原来对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝. 在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读回来

IOS多线程知识总结/队列概念/GCD/串行/并行/同步/异步

进程:正在进行中的程序被称为进程,负责程序运行的内存分配;每一个进程都有自己独立的虚拟内存空间: 线程:线程是进程中一个独立的执行路径(控制单元);一个进程中至少包含一条线程,即主线程. 队列:dispatch_queue_t,一种先进先出的数据结构,线程的创建和回收不需要程序员操作,由队列负责. 串行队列:队列中的任务只会顺序执行(类似跑步) dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial); 并

Node.js 实现串行化流程控制

为了演示如何实现串行流程控制,我们准备做个小程序,让它从一个随机选择的RSS预定源中获取一片标题和URL,并显示出来. RSS预定源列表放在rss_feeds.txt文件中,内容如下: http://feed.cnblogs.com/blog/u/376823/rss http://lambda-the-ultimate.org/rss.xml 运行程序前我们需要安装两个模块:request模块是个经过简化的HTTP客户端,你可以用它获取RSS数据.htmlparser模块能把原始的RSS数据转

对象的序列化(串行化)分析(一)

对象的序列化(串行化)序列化概念:(1)对象的寿命通常随着生成该对象的程序的终止而终止.有时候,可能需要将对象的状态保存下 来,在需要时再将对象恢复.我们把对象的这种能记录自己的状态以便将来再生的能力.叫作对象的持续性(persistence).对象通过写出描述自己状 态的数值来记录自己 ,这个过程叫对象的串行化(Serialization-连续) .(2)一个对象随着创建而存在,随着程序结束而结束.那 如果我要保存一个对象的状态呢?Java序列化能够将对象的状态写入byte流存储起来,也从其他

linux总结应用之三 安装和配置串行,并行链路

 (一)远程站的设置: 最简单的做法是在远程的机器专为拨号连接建立PPP 登陆项 : ppp:off:700:700:ppp acount:/home/ppp:home/ppp/ppplogin 为账号建立起始目录; # mkdir   /home/ppp #  chown  ppp. /home/ppp 注意在新加的行中,将下列程序作为登陆的shell: /home/ppp/ppplogin 实际上,它不是shell程序.而是在远程机上用来启动pppd守护程序的script. 它的典型形式如下

IOS多线程知识总结/队列概念/GCD/主队列/并行队列/全局队列/主队列/串行队列/同步任务/异步任务区别(附代码)

进程:正在进行中的程序被称为进程,负责程序运行的内存分配;每一个进程都有自己独立的虚拟内存空间 线程:线程是进程中一个独立的执行路径(控制单元);一个进程中至少包含一条线程,即主线程 队列 dispatch_queue_t,队列名称在调试时辅助,无论什么队列和任务,线程的创建和回收不需要程序员操作,有队列负责. 串行队列:队列中的任务只会顺序执行(类似跑步) dispatch_queue_t q = dispatch_queue_create(“....”, DISPATCH_QUEUE_SER

.Net里的哈希表和串行化

.Net里的哈希表和串行化 介绍 本文介绍了,在.net里,使用哈希表和串行化的C#用法.这里使用的示例应用程序是一个电话簿.电话簿应用程序,是一个控制台的程序.它允许你添加,查看,列出和删除它里面的姓名和电话号码. 哈系表是"键-值"对的集合.在.net里,类Hashtable是哈希表的实现.通过调用Add方法,传递你想添加的键值对,可以完成添加.作为键来使用的这些对象,必须实现Object.Equals 和Object.GetHashCode方法. private Hashtabl

同步,异步,串行队列,并发队列,全局队列,主队列等概念的总结

同步,异步,串行队列,并发队列,全局队列,主队列等概念的总结 在GCD函数中, 我们常常碰到同步,异步,串行队列,并发队列,全局队列,主队列等概念,而这些概念又常常组合在一起, 十分头疼, 这篇文章就来梳理一下这些烦人的概念. 不想看长篇大论的, 直接看文章末尾的表格即可! 在此之前, GCD中还涉及到两个十分重要的概念, 就是任务和队列 任务(Task): 你需要执行的操作 队列(Queue): 存放任务的容器 GCD中两个重要的函数, 一个同步执行, 一个异步执行 dispatch_asyn