做完了 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. Entity Framework 4、5 多字段排序

    public interface IOrderByExpression<TEntity> where TEntity : class { IOrderedQueryable<TEnt ...

  2. 设计模式之美:Chain of Responsibility(职责链)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):实现后继者链. 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系. 将这些对象连成一条链,并沿着这条 ...

  3. NODE-WEBKIT教程(12)全屏

    node-webkit教程(12)全屏 文/玄魂 目录 node-webkit教程(12)全屏 前言 12.1  和全屏有关的三个api Window.enterFullscreen() Window ...

  4. [Hyper-V]使用操作系统模板创建新的虚拟机

    描述: 为了节省空间和时间的目的,先在Hyper-V里创建一个干净的操作系统,以后再创建虚拟机时都基于此操作系统,节省了安装Windows的时间 另外创建其它虚拟机的时候,也以上述虚拟机的磁盘为基础盘 ...

  5. ActiveMQ第三弹:在Spring中使用内置的Message Broker

    在上个例子中我们演示了如何使用Spring JMS来向ActiveMQ发送消息和接收消息.但是这个例子需要先从控制台使用ActiveMQ提供的命令行功能启动一个Message Broker,然后才能运 ...

  6. Javascript中prototype属性的详解

    原文链接:http://www.cnblogs.com/Uncle-Keith/p/5834289.html 在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象 ...

  7. 深入理解javascript事件流

    摘要:事件流这个东西是比较重要的,为了让自己更加理解js中的事件流,必须整理整理,梳理一下事件流的各种东西啊.本文大部分内容参考<javascript高级程序设计第三版> 先来一段书里的原 ...

  8. 关于解决keil4和mdk共存后51不能使用go to definition Of 'XXXXXX'问题

    自己安装keil4和mdk共存后,(我是先安装的keil 后安装的 MDK),在51单片机工程里不能使用go to definition Of 'XXXXXX'问题, 类似的如图 已经困扰了好长时间, ...

  9. paip.字符串操作uapi java php python总结..

    paip.字符串操作uapi java php python总结.. java and php 相互转换.. import strUtil>>>  requiry(strUtil.p ...

  10. 使用socket.io开发简单群聊功能

    1.新建package.json文件: { "name": "socket-chat-example", "version": " ...