做完了 scala parallel 课程作业后,觉得 scala 写并发程序的便捷性是 java 永远都追不上的。scala 的Future 和 Promise,java 里 Future 和 CompleteFuture 实现了类似的功能,但是使用的便捷性还差的很远,java.util.Future 本身 API 较少,不支持链式操作。CompleteFuture 丰富了 Future 的 API,但是也不好用。

这里用 Scala parallel 学到的东西计算 Inversion. Inversion 叫做逆序对,它的 nlogn 算法的思想是在 merge sort 的 Merge 阶段计算逆序对的个数。先列出单线程解法。

public static long sort(List<Integer> numbers, int left, int right) {

  if(left >= right) return 0L;

  if(left + 1 == right) return 0L;

  int mid = (right - left) / 2 + left;

  long leftInversion = sort(numbers, left, mid);

  long rightInversion = sort(numbers, mid, right);

  long mergeInversion = merge(numbers, left, mid, right);

  return (leftInversion + rightInversion + mergeInversion);

}

public static long merge(List<Integer> numbers, int left, int mid, int right) {

  List<Integer> buf = new ArrayList<>();

  int leftCursor = left, rightCursor = mid;

  long inversion = 0;

  

  while(leftCursor < mid && rightCursor < right) {

    if(numbers.get(leftCursor) <= numbers.get(rightCursor)) buf.add(numbers.get(leftCursor ++));

    else {

      buf.add(numbers.get(rightCursor ++));

      inversion += (mid - leftCursor);

    }

    while(leftCursor < mid) buf.add(numbers.get(leftCursor ++));

    while(rightCursor < right) buf.add(numbers.get(rightCursor ++));

    for(int i = 0; i < (right - left); i ++) numbers.set(i+left, buf.get(i));

    return inversion;

  }

}

做 benchmark 一定要注意同一段程序要 run 多遍,以最后一遍的运行时间为准,因为预热阶段包括对内存的填充,线程的创建等等。

在我的 4 核 i7 mac 上跑了三轮,10万数字的 inversion, 时间分别是 100ms, 70ms, 40ms.

然后是并行解法。并行解法使用了 ForkJoinPool,别的 threadPool 也是一样的,但是性能上是否有区别就不知道了。

为了避免每次执行任务都要创建 ForkJoinTask, 先写一个 wrapper.

public abstract class TaskScheduler {

  public abstract <T> ForkJoinTask<T> schedule(Function<Void, T> func);

}

public class DefaultTaskScheduler extends TaskScheduler {

  public <T> ForkJoinTask<T> schedule(Function<Void, T> func) {

    ForkJoinTask<T> task = new ForkJoinTask<T>() {

      protected T compute() { return func.apply(null); }

    };

    ForkJoinCom.pool.execute(task);

    return task;

  }

}

有了这个 Wrapper 以后,就可以通过 schedule 函数直接把运算逻辑变成 ForkJoinTask。

merge 是顺序执行的,写不出它的并行实现,但是 sort 函数是分而治之算法,每次把 List 划分为不相交的两段,可以并行的对这两段排序。

public static long parSort(List<Integer> nums, int left, int right, int threshold) {

  if(right - left <= threshold) return Inversion.sort(nums, left, right);

  int mid = (right - left) /2 + left;

  ForkJoinTask<Long> leftTask = ForkJoinCom.scheduler.schedule(Void -> parSort(nums, left, mid, threshold));

  ForkJoinTask<Long> rightTask = ForkJoinCom.scheduler.schedule(Void -> parSort(nums, mid, right, threshold));

  long leftInversions = leftTask.join();

  long rightInversions = rightTask.join();

  long mergeInversions = Inversion.merge(numbers, left, mid, right);

  return leftInversions + rightInversions + mergeInversions;

}

到这里,并行解法就算写完了,但是性能提升的并不明显。尝试调整 threshold, 调整 ForkJoinPool 的线程数目,效果依然不明显。回忆 scala 作业题里老师给出的实现,突然想到,当 leftTask, rightTask 正在执行的时候,当前线程只是傻等着,什么都没干,这是对 CPU 资源的浪费。照着这个思路稍微修改了下 parSort 方法:

ForkJoinTask<Long> leftTask = ForkJonCom.scheduler.schedule(Void -> parSort(nums, left, mid, threshold));
//     ForkJoinTask<Long> rightTask = ForkJonCom.scheduler.schedule(Void -> parSort(nums, mid, right, threshold));
  
long rightInversion = parSort(nums, mid, right, threshold);
long leftInversion = leftTask.join(); 
 
经过修改,parSort 的第三轮成绩是 16ms, 比单线程算法快了一倍多。

编写并发程序 Inversion的更多相关文章

  1. 【Java并发基础】利用面向对象的思想写好并发程序

    前言 下面简单总结学习Java并发的笔记,关于如何利用面向对象思想写好并发程序的建议.面向对象的思想和并发编程属于两个领域,但是在Java中这两个领域却可以融合到一起.在Java语言中,面向对象编程的 ...

  2. CSharpGL(11)用C#直接编写GLSL程序

    CSharpGL(11)用C#直接编写GLSL程序 +BIT祝威+悄悄在此留下版了个权的信息说: 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharp ...

  3. 在Linux上编写C#程序

    自从C#开源之后,在Linux编写C#程序就成了可能.Mono-project就是开源版本的C#维护项目.在Linux平台上使用的C#开发工具为monodevelop.安装方式如下: 首先需要安装一些 ...

  4. 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

      35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...

  5. 如何让VS2013编写的程序

    总体分c++程序和c#程序 1.c++程序 这个用C++编写的程序可以经过设置后在XP下运行,主要的“平台工具集”里修改就可以. 额外说明:(1)程序必须为Dotnet 4.0及以下版本.(XP只支持 ...

  6. 编写一个程序,求s=1+(1+2)+(1+2+3)+…+(1+2+3+…+n)的值

    编写一个程序,求s=1+(1+2)+(1+2+3)+…+(1+2+3+…+n)的值 1 #import <Foundation/Foundation.h>  2   3 int main( ...

  7. 在Salesforce中通过编写C#程序调用dataloadercliq的bat文件取触发调用data loader来批量处理数据

    通过这篇文章 http://www.cnblogs.com/mingmingruyuedlut/p/3413903.html 我们已经知道了Data Loader可以对Salesforce的Objec ...

  8. 转 : 用Delphi编写安装程序

    http://www.okbase.net/doc/details/931  还没有亲自验证过,仅收藏 当你完成一个应用软件的开发后,那么你还需要为该软件做一个规范化的安装程序,这是程序设计的最后一步 ...

  9. 初学编写JAVA程序

    一.编写JAVA程序 编写JAVA程序,输出一行文本信息:“Hello world”,选择编辑器eclipse,打开之后编写程序 public class Hello{ public static v ...

随机推荐

  1. 每周一道数据结构(四)A*算法&博弈树α-β剪枝

    A*算法/博弈树 前阵子考试学了A*算法.博弈树和回溯,自己真是愚蠢至极,根本没就搞明白这些,所以对于这些算法问道的话就不能说清楚,也记不住,所以才有了这篇笔记.在这里感谢面试我的那位工程师~~ A* ...

  2. [游戏学习25] MFC 橡皮筋画线效果

    >_<:这是给出窗口内外不同情况的处理展示的例子. >_<:MouseCap.h #include<afxwin.h> class CMyApp :public C ...

  3. 分布式icinga2安装与使用

    目标 配置分布式的icinga2监控系统.分布式监控适用于服务器遍布在多个区域,且需要一个master做统一管理. 搭建环境 服务器 系统: ubuntu 15.04/ubuntu 14.04 ici ...

  4. Sharing count on Facebook, Twitter, and LinkedIn

    最近一段时间一直在研究有关Social Network的东西,这里有几个在当前国外主流社交网站上用来显示分享数量的API,记录一下,今后可能会用得着. Facebook Facebook将FQL(Fa ...

  5. js如何实现继承

    js继承有5种实现方式:1.继承第一种方式:对象冒充  function Parent(username){    this.username = username;    this.hello = ...

  6. atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系

    atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系 1. 服务器控件是可被服务器理解的标签.有三种类型的服务器控件: 1 1.1. HTML 服务器控件  ...

  7. 深入理解HTML5:语义、标准与样式(勇猛精进早登大师殿堂创最优品质交互)

    深入理解HTML5:语义.标准与样式(勇猛精进早登大师殿堂创最优品质交互) [美]布拉德福(Bradford,A.) [美]海涅(Haine,P.)著 高京译 ISBN 978-7-121-20552 ...

  8. 海康威视 NET_DVR_FindNextFile 的错误

    public struct NET_DVR_FIND_DATA { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = )] /// <summary ...

  9. spring 配置定时任务

    spring的定时任务配置分为三个步骤:1.定义任务2.任务执行策略配置3.启动任务1.定义任务 <!--要定时执行的方法--> <bean id="testTaskJob ...

  10. 在Servlet使用getServletContext()获取ServletContext对象出现java.lang.NullPointerException(空指针)异常的解决办法

    今天遇到了一个在servlet的service方法中获取ServletContext对象出现java.lang.NullPointerException(空指针)异常,代码如下: 1 //获取Serv ...