並發編程,是老生常談的問題了,並發編程能夠真正的讓多核cpu發揮最大的優勢。
現在我們來玩一下Java Fork/join 並發編程模型^_^
Fork/Join框架是Java7提供的一個用於並行計算的框架,它主要是用於把一個大任務拆分為若干個小任務,然后把若干個小任務的結果再匯總為大任務的結果。
這種並行模式特別適合於基於分治法(Divide and conquer)的計算。
任務拆分如圖:
這樣能夠把一個任務拆分成多個互相獨立不影響的子任務,能夠進行並行計算。
1.如何利用Fork/Join來進行編程?
先看兩個類 RecursiveTask和 RecursiveAction,繼承者兩個類(兩者區別在於前者重寫方法有返回值,后者無),然后重寫compute方法就可以進行並行編程,非常方便。
代碼如圖:
import java.util.concurrent.RecursiveAction; import java.util.concurrent.RecursiveTask; public class RecursiveTaskTest extends RecursiveTask<Object> { @Override protected Object compute() { return null; } } class RecuriveActionTest extends RecursiveAction { @Override protected void compute() { } }
我們現在使用上述模型來對我們平時常用的快速排序(Quick Sort)算法進行改進。
(不了解快排怎么實現的自行補習,傳送門http://blog.csdn.net/morewindows/article/details/6684558)
一般地,快速排序中又如下代碼:
private void originalQuickSort( int[] array, int start, int end ) { if( start > end ) { return; } int p = partition( array, start, end ); originalQuickSort( array, start, p - 1 ); originalQuickSort( array, p + 1, end ); }
存在這拆分子任務的情況,所以我們可以對傳統快排進行改進,把此代碼修改成如下形式(基於RecusiveAction)
protected void compute() { if( start > end ) { return; } int p = partition( array, start, end ); new QuickSortTest( array, start, p - 1 ).fork(); new QuickSortTest( array, p + 1, end ).fork(); }
然后簡單快排、JDK內置排序、並行編程快排三個排序進行效率比較(n=10^8):
(由於是在windows下編寫的程序,可以利用window的任務管理器觀看cpu負載情況,並且能夠反映出Fork/Join框架是否真正的利用了多核心cpu的優勢。)
機器配置:
內存:16G
Cpu:i7-4790 3.6Ghz
完整測試代碼:
(為了保證變量一致,設置三個相同的數組進行排序)
import java.util.Arrays; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; import java.util.concurrent.TimeUnit; public class QuickSortTest extends RecursiveAction { public int array[] = null; private int start; private int end; public QuickSortTest( int[] array, int start, int end ) { this.array = array; this.start = start; this.end = end; } public QuickSortTest( int[] array ) { this.array = array; } private int partition( int[] array, int beg, int end ) { int first = array[ beg ]; int i = beg, j = end; while( i < j ) { while( array[ i ] <= first && i < end ) { i++; } while( array[ j ] > first && j >= beg ) { j--; } if( i < j ) { array[ i ] = array[ i ] ^ array[ j ]; array[ j ] = array[ i ] ^ array[ j ]; array[ i ] = array[ i ] ^ array[ j ]; } } if( j != beg ) { array[ j ] = array[ beg ] ^ array[ j ]; array[ beg ] = array[ beg ] ^ array[ j ]; array[ j ] = array[ beg ] ^ array[ j ]; } return j; } private void originalQuickSort( int[] array, int start, int end ) { if( start > end ) { return; } int p = partition( array, start, end ); originalQuickSort( array, start, p - 1 ); originalQuickSort( array, p + 1, end ); } @Override protected void compute() { if( start > end ) { return; } int p = partition( array, start, end ); new QuickSortTest( array, start, p - 1 ).fork(); new QuickSortTest( array, p + 1, end ).fork(); } public static void main( String[] args ) { int LENGTH = 50000000 * 2; int[] array1 = new int[ LENGTH ]; int[] array2 = new int[ LENGTH ]; int[] array3 = new int[ LENGTH ]; for( int i = 0; i < LENGTH; i++ ) { int n = ( int )( Math.random() * Integer.MAX_VALUE ) % Integer.MAX_VALUE; array1[ i ] = n; array2[ i ] = n; array3[ i ] = n; } System.out.println( "run" ); //簡單快排 QuickSortTest qst = new QuickSortTest( array2 ); long startTime = System.currentTimeMillis(); qst.originalQuickSort( array2, 0, array2.length - 1 ); System.out.println( "original quick sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" ); //Fork/Join並行計算版本 startTime = System.currentTimeMillis(); ForkJoinPool fjp = new ForkJoinPool(); fjp.submit( new QuickSortTest( array1, 0, array1.length - 1 ) ); fjp.shutdown(); try { fjp.awaitTermination( 10, TimeUnit.SECONDS ); } catch( Exception e ) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println( "multiThread quick sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" ); //jdk排序 startTime = System.currentTimeMillis(); Arrays.sort( array3 ); System.out.println( "jdk sort : " + ( float )( System.currentTimeMillis() - startTime ) / 1000f + "s" ); } } //original quick sort : 13.032s //multiThread quick sort : 3.502s //jdk quick sort : 9.033s
我們觀察cpu的負載情況:
1.簡單快排
可以看出,cpu整體使用率是有16%左右,可以肯定cpu還是保留了實力的,結果是13s左右完成排序。
2.並行計算版本
毫不誇張的說,直接使用率直線上升,最高時可以達到100%的使用率,cpu是滿載工作的,並且可以看出八個核心的使用率都上來了,充分的證明Fork/Join框架是真正的利用了多個核心的計算能力。結果3.5s左右排序。
(*****Ps.理論上應該是快了8倍左右,實驗結果只有4倍左右,其余的消耗應該是在輪轉調度上面。******)
3.JDK內置排序
JDK內置排序非常復雜,進行了各種優化(插排,TimSort,雙軸,三軸快排等),具體我就不多累贅,有興趣的可以自行研究,結果9s左右完成排序。
總結:
利用多核的技能能力進行並行計算在速度上還是很有優勢的。所以,在擁有一台多核計算機進行編程的時候應該多想一想是否充已經分利用多核計算機的計算能力來進行編程了嗎?