并行计算实验-串、并行排序算法

并行实验报告

一、项目背景

项目要求实现快速排序、枚举排序、归并排序三种排序方法的串行和并行算法,并且进行性能比较和优化分析。其中数据集`random.txt`,当中包含30000个乱序数据,数据的范围是[-50000,50000],数据间以空格“ ”分隔。

具体要求:

1. 用Java多线程或者C#多线程模拟并行处理(推荐用Java)。

2. 说明程序执行方式,记录在ReadMe.txt中。

3. 读取乱序数据文件`random.txt`,排序完成后输出排序文件`order\*.txt`。(需提交六份order\*.txt,命名为order1.txt,order2.txt…以此类推)

4. 比较各种算法的运行时间,请将运行时间记录在2*3的表格中。行分别表示串行、并行,列分别表示快速排序、枚举排序、归并排序。

5. 撰写实验报告,包括并行算法的伪代码、运行时间、技术要点(如性能优化方法)等,结合各自的实验设备(如多核处理器)上的实验结果进行优化,并在实验报告中针对实验结果进行分析(考虑到并行算法多线程在单核处理器中的并行开销,有可能性能会比串行算法下降)。

6. 独立完成实验,杜绝抄袭。

二、算法伪代码

2.1 串行快速排序



```java

function quicksort(L, start, end):

if start < end :

p = partition(L, start, end)

quicksort(L, start, p-1)

quicksort(L, p+1, end)

end if

end function

```




````java

function partition(L, start, end):

pivot = start

i = start;

? ? for j = start to end -1:

? ? if L[j] < pivot:

? ? ? ? ? ? swap(A[i], A[j])

? ? ? ? end if

i = i + 1

? ? end for

? ? swap(A[i], A[end])

return i

end function

````



2.2 串行枚举排序


````java

function RankSort(L, start, end):

for i = start to end:

count = 1

for j= start to end:

if L[i] > L[j] || (L[i] == L[j] && i > j):

x = x + 1;

end if

? ? ? ? end for

? ? ? ? R[i] = L[i]

? ? end for

end function

````


2.3 串行归并排序


```java

function MergeSort(L, start, end):

if start < end:

m = (start + end) / 2

MergeSort(L, start, m)

MergeSort(L, m + 1, end)

Merge(L, start, m, end)

end if

end function

```



```java

function Merge(L, start, m, end):

l1 = m - start +1

l2 = end - m

A1 = L[start : m+1]

A2 = L[m+1 : end+1]

i = j = 0

for k = start to end:

if A1[i] < A2[j]:

L[k] = A1[i]

i = i + 1

? ? ? ? else:

? ? ? ? ? ? L[k] = A2[j]

? ? ? ? ? ? j = j + 1

? ? ? ? end if

? ? end for

end function

```


2.4 并行快速排序


```java

function ParallelQuickSort(L,i, j, m, 0):

if m = 0:

return;

else:

p = partition(L, i, j)

pi send L[p + 1 : j] to pi+1

ParallelQuickSort(L, i, p - 1, m - 1, id)

? ? ? ? ParallelQuickSort(L, r + 1, j, m - 1, id + 1)

? ? ? ? pi+1 send L[p + 1 : j] to pi

? ? end if

```


2.5 并行枚举排序


```java

function ParallelRankSort(L, n):

P0 send L to P1, P2 ...,Pn

for all Pi where 1<= i <= n para-do:

count = 1

for j = 0 to n do:

if L[i] > L[j] || (L[i] == L[j] && i > j):

k = k + 1

end if

end for

end for

P0 merge the result

end function

```


2.6 并行归并排序


```java

function ParallelMergeSort(L, start, end, k, id = 1):

if start< end :

m = (start + end) / 2

if id + 1 <= m:

Pid+1 do ParallelMergeSort(L, start, m, k, id + 1)

Pid+1 do ParallelMergeSort(L, m + 1, end, k, id + 2)

Set Barrier

Merge(L, start, m, end)

else:

MergeSort(L, start, m)

MergeSort(L, m + 1, end)

Merge(L, start, m, end)

end if

end if

end fucntion

```


三、Java实现

Java 具有丰富的多线程机制,可以支持对线程的调度、执行和通信。本次实验主要使用ForkJoinPool维护线程池。

针对多线程并发编程,Java8 当中的 ForkJoinPool可以支持将大任务分解成小任务再并发执行。ForkJoinPool是一种支持任务分解的线程池,当提交给他的任务“过大”,他就会按照预先定义的规则将大任务分解成小任务,多线程并发执行。 一般要配合可分解任务接口ForkJoinTask来使用,ForkJoinTask有两个实现它的抽象类:Recursive Action和Recursive Task,其区别是前者没有返回值,后者有返回值。

下面简单介绍一些在实验当中使用到的Java实现底层的使用方法。

ForkJoinPool-RecursiveTask


```java

public class ParallelMergeSort<N extends Comparable<N>> extends RecursiveTask<List<N>>

{

? ? @Override

? ? protected List<N> compute(){

? ? ? ? ? ? if(this.elements.size() <= 1){

? ? ? ? ? ? return this.elements;

? ? ? ? }

? ? ? ? else{

? ? ? ? ? ? final int pivot = this.elements.size() / 2;


? ? ? ? ? ? ParallelMergeSort<N> leftTask =

? ? ? ? ? ? new ParallelMergeSort<N>(

? ? ? ? ? ? this.elements.subList(0, pivot));


? ? ? ? ? ? ParallelMergeSort<N> rightTask =

? ? ? ? ? ? new ParallelMergeSort<N>(

? ? ? ? ? ? this.elements.subList(pivot, this.elements.size()));

? ? ? ? ? ? leftTask.fork();

? ? ? ? ? ? rightTask.fork();

? ? ? ? ? ? List<N> left = leftTask.join();

? ? ? ? ? ? List<N> right = rightTask.join();

? ? ? ? ? ? merge(left, right);

? ? ? ? ? ? return this.elements;

? ? ? ? }

? ? }

}

```


在实现ParallelMergeSort的过程当中,我使用了RecursiveTask,通过让ParallelMergeSort继承RecursiveTask实现对多线程的使用,与RecursiveAction不同的是,RecursiveTask有返回值。所以我们可以看见在join时会有返回的List。

ForkJoinPool-RecursiveAction


```java

public class ParallelQuickSort extends RecursiveAction{

? ? @Override

? ? protected void compute() {

? ? ? ? quickSort(elements, start, end);

? ? }

}

```


RecursiveAction与RecursiveTask不同点就在于,它没有返回值。

四、性能比较与分析

性能比较

在双核八处理器的机器上,在保证正确性的条件下,分别进行30次实验,取平均值,可以得到如下耗时比较表格。在比较的过程当中,我还添加比较了Java 自带库当中的`Arrays.sort()`和`Arrays.parallelsort()`,以及优化之后使用DoubleMerge算法的并行归并排序。

|? ? ? ? ? ? ? ? ? ? ? | 串行? ? ? | 并行? ? |

| --------------------- | --------- | -------- |

| Quick Sort? ? ? ? ? ? | 12.4 ms? | 47.4 ms? |

| Merge Sort? ? ? ? ? ? | 16.3 ms? | 90.6 ms? |

| Rank? ? ? ? ? ? ? ? ? | 4893.8 ms | 472.7 ms |

| Java Library? ? ? ? ? | 10.0 ms? | 17.4 ms? |

| DoubleMerge(Parallel) | ----? ? ? | 14.2 ms? |

性能分析

从最终实验结果来看,并行条件下,枚举排序的效果比较明显,比在串行条件下的效率提高了90.35% ,并行的提速效果显著。但是我们还是可以看见,快速排序和归并排序的效果并理想,甚至在并行条件下的速度要远低于在串行条件下的速度。

一个可能的解释就是在部分情况下,当单核并行程序转变成并行执行任务时,创建新线程和新任务以及拷贝,等待的通信开销大大超出计算的时间开销。从实验实现的具体底层来看,因为在ForkJoinPool的运行过程中,会创建大量的子任务。而当他们执行完毕之后,会被垃圾回收。这一定程度上导致速度上的下降。

快速排序

快速排序的速度不够理想,结合具体情况分析,我认为可能是由于以下一些原因。

- 从Java并行实现机制上来讲,使用ForkJoinPool使用过程当中会需要创建大量的子任务,创建子任务的过程会导致效率的降低。事实上,使用ForkJoinPool需要子任务之间相对均衡。但是在quick sort的过程中,子任务的规模不均匀。

- 从数据量的角度来看,当前数据量仅仅为30000,数据量的大小过小导致无法体现并行算法的优越性。

- 快速排序当中的串行分量有点多,像函数`Partition`依赖串行实现,效果并不是特别好。

归并排序

归并排序的速度不够理想,结合具体情况分析,我认为可能是由于以下一些原因。

- 从数据量的角度来看,和快速排序的情况相似,也是数据量的问题,当前数据量仅仅为40000,数据量的大小过小导致无法体现并行算法的优越性。

- 从算法实现来看,在归并排序的实现过程当中,串行分量过多,比如`Merge` 操作,依赖串行实现,影响了整体的效率。

- 从算法流程上来看,归并排序需要的同步障较多,进程之间相互等待,可能会造成时间上的浪费

- 传统的并行算法,在`Merge`的过程当中,每一次merge都会导致增加一半的处理器处于闲置状态,这样就会导致使用效率不高,

Double Merge

针对这些问题,我发现有一个算法`DoubleMerge`可以解决。这个算法的主要特点就是每个合并操作可以由两个线程同时执行。一个线程可以重复获得两个排序子数组的最小值。直到它合并了一半的元素。另一个线程可以重复获取最多两个已排序的子数组。直到它合并另一半元素。

双线程同时执行合并时,它们需要相互等待。其中等待算法如下。


```

Merging starts{

? Merge:

? ? ? Thread 1: merge mins

? ? ? Thread 2: merge maxes

? Synchronize:

? ? ? Two threads wait each other

? Copy back:

? ? ? Thread 1: copy back first half

? ? ? Thread 2: copy back second half

? Synchronize:

? ? ? Two threads wait each other

} Merging ends

```


该算法性能超过Java自带库并行算法。算法详细细节可以看这一篇论文。

枚举排序

- 枚举排序的并行化转换思路比较清晰,原算法本身通过将每一个元素遍历一遍数组得到合理的位置。这样的流程天生适合并行化的转换。但是受限于排序算法自身设计的缺陷,不能够取得较好的效果。

五、优化思路

思路一

在一些算法当中可以将一些流程做出改进,比如当子问题规模降低到较小程度时,可以使用简单排序,而不是创建新进程递归。比如在归并排序中,当问题的规模降低到只有32个左右时,可以采用简单快速排序,提高效率。

思路二

与思路一相似,在实际操作实现并行排序当中,可以借鉴Java Library库当中的Parallel实现,首先判断问题的规模。如果规模较小,可以直接串行排序;规模较大的情况下,可以使用并行排序。

思路三

就如同DoubleMerge算法,我们在发现现有并行算法当中串行量太多时,需要根据性能加速比公式,减少串行分量。而减少串行的分量的一个清晰的想法就是,将串行分量转化成并行分量,好比将MergeSort当中的Merge转化成并行分量一样,我们也可以尝试将Quick Sort当中的Partition函数做并行化处理,一样可以获得较好的效果。

思路四

在我们现有的多线程体系当中,所有线程的优先级都是一样的,我们可以建立起线程的优先级,建立线程树,让一些线程得到较高的优先级,优先处理。当然了,这就已经不是本次实践考虑的了。

六、实验感想

实验本身并不复杂,课堂介绍过详细的实现流程,所以实验的操作难度并不大。但是我还是有一些建议。

- 实验数据可以不要求使用一样的数据, 应该支持使用自身创建的数据。

? 这样一来,可以设计出较大规模的数据集,使得充分体现并行算法的优越性,同时在设计数据集的过程当中,二来也可以体会到不同倾向的数据适合什么样的排序算法。

- 实验语言可以适当的放宽,在我的实现过程当中,我一度想要使用GPU进行排序,但是考虑到不同语言对CUDA的支持不一样,同时没有支持使用C++,我放弃了这个想法。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351