0

「Java 8 In Action」(Raoul-Gabriel Urma、Mario FuscoおよびAlan Mycroft著、セクション5.6.3、116および117ページ)を読んでいます。 "ピタゴラス三倍"。ページ116は最初の試行を示し、117ページはこれらのトリプルを生成するための改良された試行を示しています。両方とも ".rangeClosed()"メソッドを使用しています。数値ストリームの最適化例

私は本を超えていくつかの最適化を見つけました。私はここでそれらを共有したいと思います。私はいくつかのシンプルな "System.currentTimeMillis()"の計算を行って、私の修正が改良されたかどうかを確認しました。このコードの改善、説明、またはメトリクスを改善できますか?

public void test() { 

    long time1 = System.currentTimeMillis(); 

    /* 
    * From text, page 116 
    */ 
    IntStream.rangeClosed(1, 100).boxed() 
     .flatMap(a -> IntStream.rangeClosed(a, 100) 
       .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) 
       .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)}) 
     ) 
     .forEach(c -> System.out.println("["+c[0]+" "+c[1]+" "+c[2]+"]")); 

    long time2 = System.currentTimeMillis(); 

    System.out.println(); 

    long time3 = System.currentTimeMillis(); 

    /* 
    * From text, page 117, I added "map(...)" so that end result are ints, not doubles 
    */ 
    IntStream.rangeClosed(1, 100).boxed() 
     .flatMap(a -> IntStream.rangeClosed(a, 100) 
       .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) 
       .filter(t -> t[2] % 1 == 0) 
       .map(b -> new int[]{(int)b[0], (int)b[1], (int)b[2]}) 
     ) 
     .forEach(c -> System.out.println("["+c[0]+" "+c[1]+" "+c[2]+"]")); 

    long time4 = System.currentTimeMillis(); 

    System.out.println(); 

    long time5 = System.currentTimeMillis(); 

    /* 
    * My optimization attempt #1: now mapToObj(...) has c%1!=0 conditional, filter checks array element not null 
    */ 
    IntStream.rangeClosed(1, 100).boxed() 
    .flatMap(a -> IntStream.rangeClosed(a, 100) 
       .mapToObj(b -> { 
        double c = Math.sqrt(a*a + b*b); 
        return new Object[]{a, b, c % 1 == 0 ? (int)c : null}; 
       }) 
       .filter(d -> d[2] != null) 
       .map(e -> new int[]{(int)e[0], (int)e[1], (int)e[2]}) 
    ) 
    .forEach(f -> System.out.println("["+f[0]+" "+f[1]+" "+f[2])); 

    long time6 = System.currentTimeMillis(); 

    System.out.println(); 

    long time7 = System.currentTimeMillis(); 

    /* 
    * My optimization attempt #2: now mapToObj(...) has c%1!=0 conditional, filter checks "array element" not 0 
    */ 
    IntStream.rangeClosed(1, 100).boxed() 
     .flatMap(a -> IntStream.rangeClosed(a, 100) 
       .mapToObj(b -> { 
        double c = Math.sqrt(a*a + b*b); 
        return new int[]{a, b, c % 1 == 0 ? (int)c : 0 }; 
       }) 
       .filter(t -> t[2] != 0) 
     ) 
     .forEach(d -> System.out.println("["+d[0]+" "+d[1]+" "+d[2]+"]")); 

    long time8 = System.currentTimeMillis(); 

    System.out.println(); 

    long time9 = System.currentTimeMillis(); 

    /* 
    * My optimization attempt #3: now mapToObj(...) has c%1!=0 conditional, filter checks "collection element" not null 
    */ 
    IntStream.rangeClosed(1, 100).boxed() 
     .flatMap(a -> IntStream.rangeClosed(a, 100) 
       .mapToObj(b -> { 
        double c = Math.sqrt(a*a + b*b); 
        return (c % 1 != 0) ? null : new int[]{a, b, (int)c}; 
       }) 
       .filter(t -> t != null) 
     ) 
     .forEach(d -> System.out.println("["+d[0]+" "+d[1]+" "+d[2]+"]")); 

    long time10 = System.currentTimeMillis(); 

    System.out.println(); 

    long timeDelta1 = time2 - time1; 
    long timeDelta2 = time4 - time3; 
    long timeDelta3 = time6 - time5; 
    long timeDelta4 = time8 - time7; 
    long timeDelta5 = time10 - time9; 

    System.out.println("timeDelta1: " + timeDelta1 + ", timeDelta2: " + timeDelta2 + ", timeDelta3: " + timeDelta3 + ", timeDelta4: " + timeDelta4 + ", timeDelta5: " + timeDelta5); 

} 

public static void main(String[] args){ 
    ReduceTest reduceTest = new ReduceTest(); 
    reduceTest.test(); 
} 

注:「return;」を使用することができます。 ".forEach()"メソッドではなく、 ".mapToInt()"メソッドでは使用できません。 "return;"を使う".mapToInt()"メソッドに渡されたラムダでは、 ".filter()"メソッドを持つ必要がなくなります。それはストリームapiの改善となるようです。

+1

私はこの質問は、より良いで議論されていると思う:http://codereview.stackexchange.com/ – Flown

+0

@Flown、良い点、私はこれを行っています。 –

+0

@Alexis、ありがとう、そのストリームタグはjava-streamでなければなりません、あなたはもちろんです。 –

答えて

3

まず、「ベンチマーク」に大きな欠陥があります。ストリームAPIメソッドなど、より多くの共有コードが実行されるとコンパイル/最適化されたため、最終バリアントの方が高速になっている可能性が高くなります。 “How do I write a correct micro-benchmark in Java?”

さらに、数値が整数であるかどうかをテストする場合は、「%1」は不要です。 intにキャストすると小数部分が削除され、元のdoubleと比較されます。

IntStream.rangeClosed(1, 100).boxed() 
    .flatMap(a -> IntStream.rangeClosed(a, 100) 
      .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) 
      .filter(t -> ((int)t[2]) == t[2]) 
      .map(arr -> String.format("[%.0f %.0f %.0f]", arr[0], arr[1], arr[2])) 
    ) 
    .forEach(System.out::println); 

しかし、もちろん、あなたが必要なことがわかっている場合:さらに、あなたはStringになり、あなたはすでにあなたが印刷しようとしているか、ということを知ったとき、int[]アレイへmapする必要はありませんこれが1であること

int[] square = new int[20001]; 
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i); 
IntStream.rangeClosed(1, 100).boxed() 
    .flatMap(a -> IntStream.rangeClosed(a, 100) 
      .filter(b -> square[a*a+b*b]!=0) 
      .mapToObj(b -> String.format("[%d %d %d]", a, b, square[a*a+b*b])) 
    ) 
    .forEach(System.out::println); 

注:その高価な関数を用いて、あるいは一般的に浮動小数点演算をすることなく製造する可能性がある場合、事前にそれを計算sqrt数回のような高価な機能は、特に、有益であり得ますいくつかのケースのうち、ネストされたforEachflatMapに代わる次のようになります。

int[] square=new int[20001]; 
IntStream.rangeClosed(1, 141).forEach(i -> square[i*i]=i); 
IntStream.rangeClosed(1, 100) 
    .forEach(a -> IntStream.rangeClosed(a, 100) 
      .filter(b -> square[a*a+b*b]!=0) 
      .forEach(b -> System.out.printf("[%d %d %d]%n", a, b, square[a*a+b*b])) 
    ); 
+1

このコードを解析する時間をとって1+です。 – Eugene

+0

@Holger、コメントいただきありがとうございます。私はあなたの洞察に感謝します。私はベンチマークに慣れていないので、私はそれについてあなたと異なっていなければなりません。 filter()メソッドのdouble型からint型へのキャストが%1比較よりも集中的で、String.format()を使った最終的なmap()呼び出しが良い方法ではないかと思います。配列値で私のメソッドを使用するよりも。実際にmap()コールは余分なステップです。しかし、私はまだあなたのアプローチに感謝しています。 –

+0

もちろん、 'double'から' int'へのキャストはモジュロ演算よりもはるかに安いですが、JVMのオプティマイザがあなたの特定の '...%1'をより効率的なものに変換することができ、おそらく正確に 'int'へのキャストまでです。配列に 'map'を持ち、その配列を' String'に変換した元のコードと比較して、 'String'への' map'がどのように「余分なステップ」であるかは分かりません。しかし、最後の例はどんなマッピングステップも必要ありません。 – Holger

関連する問題