2017-06-02 15 views
0

これは実行時間を把握するための理想的な方法ではないかもしれませんが、私は基本的に私がここでやろうとしていますが、最も古いPersonを見つけて、それが要した時間をプリントアウトされ、この次のコードスニペットstream.max()はstream.reduce()よりも常に高速ですか?

// List of persons with name and age 
    List<Person> persons = new ArrayList<>();  

    // Adding 10,000 objects 
    for(int i = 0 ; i < 10000 ; i ++) { 
     Person p = new Person(); 
     p.setName("Person " + i); 
     p.setAge(i); 
     persons.add(p); 
    } 

    long time1 = System.nanoTime(); 
    System.out.println("Time before steram.reduce()" + time1); 
    Optional<Person> o1 = Optional<Person> o1 = persons.stream().reduce(BinaryOperator.maxBy(Comparator.comparingInt(p -> p.getAge()))); 
    long time2 = System.nanoTime(); 
    System.out.println(o1.get() + "\nTime after stream.reduce() " + time2); 
    System.out.println("**** Rough execution time for stream.reduce() : " + (time2 - time1) + " nano secs"); 


    long time3 = System.nanoTime(); 
    System.out.println("Time before stream.max() " + time3); 
    Optional<Person> o2 = persons.stream().max((p01, p02) -> p01.getAge() - p02.getAge()); 
    long time4 = System.nanoTime(); 
    System.out.println(o2.get() + "\nTime after stream.max() " + time4); 
    System.out.println("**** Rough execution time for stream.max() : " + (time4 - time3) + " nano secs"); 

を持っていますstream.reduce()stream.max()を使って調べてください。

出力

Time before steram.reduce()8834253431112 
[ Person 9999, 9999] 
Time after stream.reduce() 8834346269743 
**** Rough execution time for stream.reduce() : 92838631 nano secs 
Time before stream.max() 8834346687875 
[ Person 9999, 9999] 
Time after stream.max() 8834350117000 
**** Rough execution time for stream.max() : 3429125 nano secs 

P.S.私はstream.max()stream.reduce()の順番を変えてこのコードを複数回実行し、stream.max()よりも出力を生成するのにかなり時間がかかります。

だからstream.max()常にstream.reduce()より速いですか?はいの場合は、いつstream.reduce()を使用しますか?

+1

前の時間と後の時間を比較すると、結果はより読みやすくなります。 –

+4

正確なベンチマークは実際には構築が難しいです。たとえば、比較したスニペットの順序を変更してみてください(最初に最大値にしてから減らしてください)。違いが見られるかもしれません。とにかく、maxははるかに読みやすいIMHOですので、私は最大限に行くでしょう。そして後でそれが実際に分かるならば、ソフトウェアのパフォーマンスを全体的に向上させることは間違いありません。しかし、最初にプロファイリングしてから最適化します;) – sp00m

+9

'new Date()。getTime()'は、System.currentTimeMillis()を取得する厄介な方法です。しかし、「System.nanoTime()」を使って経過時間を測定するか、「Javaで正しいマイクロベンチマークを書くにはどうすればよいですか?」(https://stackoverflow.com/questions/504103/how- i-write-a-correct-micro-benchmark-in-java) – Holger

答えて

0

reduce関数は、各繰り返しで2回評価するので、コンパイラの最適化によって結果が遅くなり、コードを再構成して結果を確認するのは、getAgeです。

また、Stream.maxは、組み込みのVM最適化の恩恵を受ける可能性があります。そのため、同等の機能を実装する代わりに、常に組み込み関数を使用する必要があります。次のように

+3

'Stream.max'は、要素ごとに' getAge'を2回呼び出します。 – Holger

+0

私はそれがよりスマートに実装されると思ったが、私はOpenJDKで見ることができるように、単にreduce(BinaryOperator.maxBy(comparator))を呼び出す。だから私の心に今唯一のことは、このインラインの 'if'分岐よりコンパレータが最適化されるかもしれないということだけです。 –

+3

それをもっとスマートに実装する方法はありません。あなたが持っているすべてのものは、「コンパレータ」です。メソッドが実際には "最大値のプロパティ"であることを知っていれば、それはよりスマートに実行できますが、 'Comparator'インターフェースは... – Holger

3

ReferencePipeline implementation of maxに見えます:

public final Optional<P_OUT> max(Comparator<? super P_OUT> comparator) { 
    return reduce(BinaryOperator.maxBy(comparator)); 
} 

だから、あなたが観察し、パフォーマンスの違いは、パフォーマンスを測定するために使用したアプローチの単なるアーティファクトです。

または、より明確に:答えはいいえです。「常により速い」とは限りません。


編集:は参考のために、ここにコードの微調整バージョンです。異なる数の要素のテストを繰り返し実行します。 これはまだ本当の、信頼性(マイクロ)ベンチマーク、一度だけ全部を実行するよりも、より信頼できるものではありません。

import java.util.ArrayList; 
import java.util.List; 
import java.util.Optional; 

public class MaxReducePerformance 
{ 
    public static void main(String[] args) 
    { 
     for (int n=500000; n<=5000000; n+=500000) 
     { 
      List<Person> persons = new ArrayList<>(); 
      for (int i = 0; i < n; i++) 
      { 
       Person p = new Person(); 
       p.setName("Person " + i); 
       p.setAge(i); 
       persons.add(p); 
      } 

      System.out.println("For " + n); 

      long time1 = System.nanoTime(); 
      Optional<Person> o1 = persons.stream().reduce((p01, p02) -> 
      { 
       if (p01.getAge() < p02.getAge()) 
        return p02; 
       return p01; 
      }); 
      long time2 = System.nanoTime(); 
      double d0 = (time2 - time1)/1e9; 
      System.out.println("Reduce: "+d0+" seconds, " + o1); 

      long time3 = System.nanoTime(); 
      Optional<Person> o2 =persons.stream().max(
       (p01, p02) -> p01.getAge() - p02.getAge()); 
      long time4 = System.nanoTime(); 
      double d1 = (time4 - time3)/1e9; 
      System.out.println("Max : "+d1+" seconds, " + o2); 
     } 

    } 
} 


class Person 
{ 
    String name; 
    int age; 

    void setName(String name) 
    { 
     this.name = name; 
    } 

    void setAge(int age) 
    { 
     this.age = age; 
    } 

    int getAge() 
    { 
     return age; 
    } 

} 

出力が継続時間は基本的に同じであることを示す必要があります。

+1

私は異なるベンチマーク結果を持っていました。最初のものは実際に速く、これは整数を直接比較し、2番目のものは0を減算して比較します(あるいは、おそらくコンパレータを使用したことによる余分な迂回によって)。これは、JIT発行コードで見られるように、最適化されていませんでした。 –

+0

@JornVernee別の振る舞いをもたらすため、最適化することはできません。しかし、ここではJITコードを分析しませんでした。ちょうどそこには(測定可能な)違いがないことを指摘したいと思いました。違いがあなたのケースで有意でしたか? – Marco13

+0

ソートされた入力で差が最も大きく、コンパレータの実装では約50%長くなり、シャッフル入力では約25%でした。私がアセンブラで見た主なものは、 'compare'メソッドがインライン化されていないということでした。これは 'BinaryOperator'のフィールドなので、定数としては見えません。 –

関連する問題