免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
從命令式編程到Fork/Join再到Java 8中的并行Streams

Java 8帶來了很多可以使編碼更簡潔的特性。例如,像下面的代碼:

Collections.sort(transactions, new Comparator<Transaction>(){  public int compare(Transaction t1, Transaction t2){    return t1.getValue().compareTo(t2.getValue());  }});

可以用替換為如下更為緊湊的代碼,功能相同,但是讀上去與問題語句本身更接近了:

transactions.sort(comparing(Transaction::getValue));

Java 8引入的主要特性是Lambda表達(dá)式、方法引用和新的Streams API。它被認(rèn)為是自20年前Java誕生以來語言方面變化最大的版本。要想通過詳細(xì)且實(shí)際的例子來了解如何從這些特性中獲益,可以參考本文作者和Alan Mycroft共同編寫的《Java 8 in Action: Lambdas, Streams and Functional-style programming》一書。

這些特性支持程序員編寫更簡潔的代碼,還使他們能夠受益于多核架構(gòu)。實(shí)際上,編寫可以優(yōu)雅地并行執(zhí)行的程序還是Java專家們的特權(quán)。然而,借助新的Streams API,Java 8改變了這種狀況,讓每個(gè)人都能夠更容易地編寫利用多核架構(gòu)的代碼。

在這篇文章中,我們將使用以下三種風(fēng)格,以不同方法計(jì)算一個(gè)大數(shù)據(jù)集的方差,并加以對(duì)比。

  1. 命令式風(fēng)格
  2. Fork/Join框架
  3. Streams API

方差是統(tǒng)計(jì)學(xué)中的概念,用于度量一組數(shù)的偏離程度。方差可以通過對(duì)每個(gè)數(shù)據(jù)與平均值之差的平方和求平均值來計(jì)算。例如,給定一組表示人口年齡的數(shù):40、30、50和80,我們可以這樣計(jì)算方差:

  1. 計(jì)算平均值:(40 + 30 + 50 + 80) / 4 = 50
  2. 計(jì)算每個(gè)數(shù)據(jù)與平均值之差的平方和:(40-50)2 + (30-50)2 + (50-50)2 + (80-50)2 = 1400
  3. 最后平均:1400/4 = 350

命令式風(fēng)格

下面是計(jì)算方差的一種典型的命令式風(fēng)格實(shí)現(xiàn):

public static double varianceImperative(double[] population){   double average = 0.0;   for(double p: population){      average += p;   }   average /= population.length;   double variance = 0.0;   for(double p: population){     variance += (p - average) * (p - average);   }   return variance/population.length;}

為什么說這是命令式的呢?我們的實(shí)現(xiàn)用修改狀態(tài)的語句序列描述了計(jì)算過程。這里,我們顯式地對(duì)人口年齡數(shù)組中的每個(gè)元素進(jìn)行迭代,而且每次迭代時(shí)更新average和variance這兩個(gè)局部變量。這種代碼很適合只有一個(gè)CPU的硬件架構(gòu)。確實(shí),它可以非常直接地映射到CPU的指令集。

Fork/Join框架

那么,如何編寫適合在多核架構(gòu)上執(zhí)行的實(shí)現(xiàn)代碼呢?應(yīng)該使用線程嗎?這些線程是不是要在某個(gè)點(diǎn)上同步?Java 7引入的Fork/Join框架緩解了一些困難,所以讓我們使用該框架來開發(fā)方差算法的一個(gè)并行版本吧。

public class ForkJoinCalculator extends RecursiveTask<Double> {   public static final long THRESHOLD = 1_000_000;   private final SequentialCalculator sequentialCalculator;   private final double[] numbers;   private final int start;   private final int end;   public ForkJoinCalculator(double[] numbers, SequentialCalculator sequentialCalculator) {     this(numbers, 0, numbers.length, sequentialCalculator);   }   private ForkJoinCalculator(double[] numbers, int start, int end, SequentialCalculator sequentialCalculator) {     this.numbers = numbers;     this.start = start;     this.end = end;     this.sequentialCalculator = sequentialCalculator;   }   @Override   protected Double compute() {     int length = end - start;     if (length <= THRESHOLD) {         return sequentialCalculator.computeSequentially(numbers, start, end);     }     ForkJoinCalculator leftTask = new ForkJoinCalculator(numbers, start, start + length/2, sequentialCalculator);     leftTask.fork();     ForkJoinCalculator rightTask = new ForkJoinCalculator(numbers, start + length/2, end, sequentialCalculator);     Double rightResult = rightTask.compute();     Double leftResult = leftTask.join();     return leftResult + rightResult;  }}

這里我們編寫了一個(gè)RecursiveTask類的子類,它對(duì)一個(gè)double數(shù)組進(jìn)行切分,當(dāng)子數(shù)組的長度小于等于給定閾值(THRESHOLD)時(shí)停止切分。切分完成后,對(duì)子數(shù)組進(jìn)行順序處理,并將下列接口定義的操作應(yīng)用于子數(shù)組。

public interface SequentialCalculator {  double computeSequentially(double[] numbers, int start, int end);}

利用該基礎(chǔ)設(shè)施,可以按如下方式并行計(jì)算方差。

 public static double varianceForkJoin(double[] population){   final ForkJoinPool forkJoinPool = new ForkJoinPool();   double total = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {     @Override     public double computeSequentially(double[] numbers, int start, int end) {       double total = 0;       for (int i = start; i < end; i++) {         total += numbers[i];       }       return total;     }  }));  final double average = total / population.length;  double variance = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {    @Override    public double computeSequentially(double[] numbers, int start, int end) {      double variance = 0;      for (int i = start; i < end; i++) {        variance += (numbers[i] - average) * (numbers[i] - average);      }      return variance;    } })); return variance / population.length;}

本質(zhì)上,即便使用Fork/Join框架,相對(duì)于順序版本,并行版本的編寫和最后的調(diào)試仍然困難許多。

并行Streams

Java 8讓我們可以以不同的方式解決這個(gè)問題。不同于編寫代碼指出計(jì)算如何實(shí)現(xiàn),我們可以使用Streams API粗線條地描述讓它做什么。作為結(jié)果,庫能夠知道如何為我們實(shí)現(xiàn)計(jì)算,并施以各種各樣的優(yōu)化。這種風(fēng)格被稱為聲明式編程。Java 8有一個(gè)為利用多核架構(gòu)而專門設(shè)計(jì)的并行Stream。我們來看一下如何使用它們來更快地計(jì)算方差。

假定讀者對(duì)本節(jié)探討的Stream有些了解。作為復(fù)習(xí),Stream<T>是T類型元素的一個(gè)序列,支持聚合操作。我們可以使用這些操作來創(chuàng)建表示計(jì)算的一個(gè)管道(pipeline)。這里的管道和UNIX的命令管道一樣。并行Stream就是一個(gè)可以并行執(zhí)行管道的Stream,可以通過在普通的Stream上調(diào)用parallel()方法獲得。要復(fù)習(xí)Stream,可以參考Javadoc文檔。

好消息是,Java 8 API內(nèi)建了一些算術(shù)操作,如max、min和average。我們可以使用Stream的幾種基本類型特化形式來訪問前面幾個(gè)方法:IntStream(int類型元素)、LongStream(long類型元素)和DoubleStream(double類型元素)。例如,可以使用IntStream.rangeClosed()創(chuàng)建一系列數(shù),然后使用max()和min()方法計(jì)算Stream中的最大元素和最小元素。

回到最初的問題,我們想使用這些操作來計(jì)算一個(gè)規(guī)模較大的人口年齡數(shù)據(jù)的方差。第一步是從人口年齡數(shù)組創(chuàng)建一個(gè)Stream,可以通過Arrays.stream()靜態(tài)方法實(shí)現(xiàn):

DoubleStream populationStream = Arrays.stream(population).parallel();

我們可以使用DoubleStream所支持的average()方法:

double average = populationStream.average().orElse(0.0);

下一步是使用average計(jì)算方差。人口年齡中的每個(gè)元素首先需要減去平均值,然后計(jì)算差的平方??梢詫⑵湟曌饕粋€(gè)Map操作:使用一個(gè)Lambda表達(dá)式(double p) -> (p - average) * (p - average)把每個(gè)元素轉(zhuǎn)換為另一個(gè)數(shù),這里是轉(zhuǎn)換為該元素與平均值差的平方。一旦轉(zhuǎn)換完成,我們就可以調(diào)用sum()方法來計(jì)算所有結(jié)果元素的和了。.

不過別那么著急。Stream只能消耗一次。如果復(fù)用populationStream,我們會(huì)碰到下面這個(gè)令人驚訝的錯(cuò)誤:

java.lang.IllegalStateException: stream has already been operated upon or closed

所以我們需要使用第二個(gè)流來計(jì)算方差,如下所示:

public static double varianceStreams(double[] population){   double average = Arrays.stream(population).parallel().average().orElse(0.0);   double variance = Arrays.stream(population).parallel()             .map(p -> (p - average) * (p - average))             .sum() / population.length;   return variance;}

通過使用Streams API內(nèi)建的操作,我們以聲明式、而且非常簡潔的方式重寫了最初的命令式風(fēng)格代碼,而且聲明式風(fēng)格讀上去幾乎就是方差的數(shù)學(xué)定義。我們?cè)賮硌芯恳幌氯N實(shí)現(xiàn)版本的性能。

基準(zhǔn)測(cè)試

我們以非常不同的風(fēng)格編寫了三個(gè)版本的方差算法。Stream版本是最簡潔的,而且是以聲明式風(fēng)格編寫的,它讓類庫去確定具體的實(shí)現(xiàn),并利用多核基礎(chǔ)設(shè)施。不過你可能想知道它們的執(zhí)行效果如何。為找出答案,讓我們創(chuàng)建一個(gè)基準(zhǔn)測(cè)試,對(duì)比一下三個(gè)版本的表現(xiàn)。我們先隨機(jī)生成1到140之間的3000萬個(gè)人口年齡數(shù)據(jù),然后計(jì)算其方差。我們使用jmh來研究每個(gè)版本的性能。Jmh是OpenJDK支持的一個(gè)Java套件。讀者可以從GitHub克隆該項(xiàng)目,自己運(yùn)行基準(zhǔn)測(cè)試。

基準(zhǔn)測(cè)試運(yùn)行的機(jī)器是Macbook Pro,配備2.3 GHz的4核Intel Core i7處理器,16GB 1600MHz DDR3內(nèi)存。此外,我們使用的JDK 8版本如下:

java version "1.8.0-ea"Java(TM) SE Runtime Environment (build 1.8.0-ea-b121)Java HotSpot(TM) 64-Bit Server VM (build 25.0-b63, mixed mode)

結(jié)果用下面的柱狀圖說明。命令式版本用了60毫秒,F(xiàn)ork/Join版本用了22毫秒,而流版本用了46毫秒。

這些數(shù)據(jù)應(yīng)該謹(jǐn)慎對(duì)待。比如,如果在32位JVM上運(yùn)行測(cè)試,結(jié)果很可能有較大的差別。然而有趣的是,使用Java 8中的Streams API這種不同的編程風(fēng)格,為在場(chǎng)景背后執(zhí)行一些優(yōu)化打開了一扇門,而這在嚴(yán)格的命令式風(fēng)格中是不可能的;相對(duì)于使用Fork/Join框架,這種風(fēng)格也更為直接。

關(guān)于作者

Raoul-Gabriel Urma 20歲開始在劍橋大學(xué)攻讀計(jì)算機(jī)科學(xué)博士學(xué)位。他的研究主要關(guān)注的是編程語言與軟件工程。他以一等榮譽(yù)生的成績獲得了倫敦帝國理工學(xué)院的計(jì)算機(jī)科學(xué)工程碩士學(xué)位,并贏得一些技術(shù)創(chuàng)新獎(jiǎng)項(xiàng)。他曾經(jīng)為Google、eBay、Oracle和Goldman Sachs等很多大公司工作過,也參與過不少創(chuàng)業(yè)項(xiàng)目。此外,他還經(jīng)常在Java開發(fā)者會(huì)議上發(fā)表講話,也是一位Java課程講師。他的Twitter:@raoulUK,網(wǎng)站。

Mario Fusco 是Red Hat的一位高級(jí)軟件工程師,主要從事Drools核心和JBoss規(guī)則引擎方面的開發(fā)工作。作為Java開發(fā)者,他有著豐富的經(jīng)驗(yàn),參與了從媒體公司到金融部門等行業(yè)的很多企業(yè)級(jí)項(xiàng)目,而且經(jīng)常作為項(xiàng)目的領(lǐng)導(dǎo)者。他的興趣包括函數(shù)式編程和領(lǐng)域特定語言(Domain Specific Language,DSL)。憑借在這兩個(gè)方面的激情,他創(chuàng)建了開源類庫lambdaj,意在在Java中為操作集合提供一種內(nèi)部Java DSL,并支持一點(diǎn)函數(shù)式編程。他的Twitter是@mariofusco。


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java四類八種基本數(shù)據(jù)類型
Java 9為何讓開發(fā)者如此興奮,來看看它的這一基本功能!
6.5 Test for Means, Variance and Proportions
Pension insurance coverage to double by 2015
英漢統(tǒng)計(jì)名詞對(duì)照
java中取小數(shù)點(diǎn)后兩位(四種方法)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服