做完了 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. 关于mvc中@Html.DropDownListFor和@Html.DropDownList默认值无法选中问题简单总结

    当我们在做类似编辑功能的时候,会给定select选中默认值,然而mvc中偶尔这个功能不能用,或者是强类型的@Html.DropDownListFor不能用.凑巧今天遇到问题,解决问题时发现了mvc的一 ...

  2. JPA 使用

    本文以JPA+Hibernate 角色与权限示例说明. 角色实体定义: @Entity @Table public class Role { private long id; private Stri ...

  3. 《介绍一款开源的类Excel电子表格软件》续:七牛云存储实战(C#)

    两个月前的发布的博客<介绍一款开源的类Excel电子表格软件>引起了热议:在博客园有近2000个View.超过20个评论. 同时有热心读者电话咨询如何能够在SpreadDesing中实现存 ...

  4. C# 类型转换问题一

    问题描述:double类型向int类型的一个转化 详细描述:课上,我们的web老师,利用C#重新温故了我们初学C语言时的一个小程序——给定成绩,有程序判定等级.学过C语言的童鞋想必都知道这个switc ...

  5. Jstat在分析java的内存GC时的应用

    jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量.使用时,需加上查看进程的进程id,和所选参数. 执行:cd $JAVA_HOME/bin中执行jstat,注意j ...

  6. Git可视化极简易教程 —— Git GUI使用方法

    前言 之前一直想一篇这样的东西,因为最初接触时,我也认真看了廖雪峰的教程,但是似乎我觉得讲得有点多,而且还是会给我带来很多多余且重复的操作负担,所以我希望能压缩一下它在我工作中的成本,但是搜索了一下并 ...

  7. webpack学习之入门实例

    webpack:前端打包神器,目前活跃度甚至超过了gulp.grunt等,使用webpack打包,简单快速,下面记录下webpack环境搭建以及基本使用: 1.首先新建一个空白目录,用于项目根目录,比 ...

  8. Liferay7 BPM门户开发之32: 实现自定义认证登陆(定制Authentication Hook)

    第一步:修改liferay-hook.xml <?xml version="1.0"?> <!DOCTYPE hook PUBLIC "-//Lifer ...

  9. PHP基础(一)

    <?php $expression = false; if($expression == true): ?> <p>This will show if the expressi ...

  10. MongoDB学习笔记——索引管理

    索引 索引能够提升查询的效率.没有索引,MongoDB必须扫描集合中的所有文档,才能找到匹配查询语句的文档. 索引是一种特殊的数据结构,将一小块数据集保存为容易遍历的形式.索引能够存储某种特殊字段或字 ...