做完了 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. 优秀的UI插件

    妹子UI: http://amazeui.org/getting-started 百度图表: http://echarts.baidu.com/ 手机UI库:https://github.com/ac ...

  2. Unity3D引用dll打包发布的问题及解决

    今年我们开始使用Unity3D开发MMORPG,脚本语言使用C#,这样我们就可以使用以往积累的许多类库.但是,在U3D中使用.NET dll的过程并不是那么顺利,比如我们今天遇到的这种问题. 一.问题 ...

  3. [C#] 编程控制笔记本蓝牙与外部蓝牙设备通信

    一.蓝牙模块XLBT232‐D01介绍(外部设备蓝牙) 1.1.蓝牙模块简介 XLBT232-D0101蓝牙模块采用CSR BlueCore 芯片,配置6-8Mbit 的软件存储空间, 支持AT 指令 ...

  4. Linux:软件安装

    Linux 上的软件安装 四种安装方式 在线安装 从磁盘安装盘deb软件包 从二进制软件包安装 从源代码编译安装 在线安装 在不同的linux发行版上面在线安装方式会有一些差异包括使用的命令及它们的包 ...

  5. phpMyAdmin导入文件突破2M大小

    一:通过phpinfo.php找到php.ini在哪个位置,注意,它并不一定在phpMyAdmin路径下: 二:修改upload_max_filesize,post_max_size,以及memory ...

  6. 单线程&浏览器多线程

    知乎答案:http://www.zhihu.com/question/31982417/answer/54136684 copy大牛的好文:from http://www.cnblogs.com/Ma ...

  7. 点击div外面该div消失

    <head> <meta charset="UTF-8"> <title></title> <script type=&quo ...

  8. shell 学习文章列表

    linux shell 逻辑运算符.逻辑表达式详解 linux shell 自定义函数(定义.返回值.变量作用域)介绍 shell export 作用 linux bash shell之declare

  9. 记一次https访问握手失败(handshake failure)

    文章作者:luxianghao 文章来源:http://www.cnblogs.com/luxianghao/p/6239518.html  转载请注明,谢谢合作. 免责声明:文章内容仅代表个人观点, ...

  10. 关于linux curl 地址参数的问题

    例如 url 为 http://mywebsite.com/index.PHP?a=1&b=2&c=3web形式下访问url地址,使用$_GET是可以获取到所有的参数然而在Linux下 ...