13

Java 8 ストリームを練習するために、次のネストされたループを Java 8 ストリーム API に変換してみました。a^b (a,b < 100) の最大桁合計を計算し、Core i5 760 で ~0.135 秒かかります。

public static int digitSum(BigInteger x)
{
    int sum = 0;
    for(char c: x.toString().toCharArray()) {sum+=Integer.valueOf(c+"");}
    return sum;
}

@Test public void solve()
    {
        int max = 0;
        for(int i=1;i<100;i++)
            for(int j=1;j<100;j++)
                max = Math.max(max,digitSum(BigInteger.valueOf(i).pow(j)));
        System.out.println(max);
    }

私の解決策は、並列処理のために高速になると予想していましたが、実際には 0.25 秒かかりました ( parallel().

int max =   IntStream.range(1,100).parallel()
            .map(i -> IntStream.range(1, 100)
            .map(j->digitSum(BigInteger.valueOf(i).pow(j)))
            .max().getAsInt()).max().getAsInt();

私の質問

  • 変換を正しく行いましたか、またはネストされたループをストリーム計算に変換するより良い方法はありますか?
  • ストリーム バリアントが古いものよりもずっと遅いのはなぜですか?
  • parallel() ステートメントが実際に時間を 0.19 秒から 0.25 秒に増やしたのはなぜですか?

マイクロベンチマークは壊れやすく、並列処理は大きな問題の場合にのみ価値があることを知っていますが、CPU の場合、0.1 秒でも永遠ですよね?

アップデート

Eclipse Kepler の Junit 4 フレームワークで測定します (テストの実行にかかった時間を示します)。

100 ではなく a,b<1000 の結果:

  • 伝統的なループ 186s
  • 順次ストリーム 193s
  • パラレル ストリーム 55s

更新 2sum+=Integer.valueOf(c+""); (Peter に感謝します!) に置き換えるsum+= c - '0';ことで、並列メソッドの 10 秒が短縮され、45 秒になりました。こんなに大きなパフォーマンスへの影響を期待していませんでした!

また、並列処理を CPU コアの数 (私の場合は 4) に減らしても、時間が 44.8 秒にしか短縮されなかったため、あまり効果がありませんでした (はい、a と b=0 を追加しますが、これはパフォーマンスの多く):

int max = IntStream.range(0, 3).parallel().
          .map(m -> IntStream.range(0,250)
          .map(i -> IntStream.range(1, 1000)
          .map(j->.digitSum(BigInteger.valueOf(250*m+i).pow(j)))
          .max().getAsInt()).max().getAsInt()).max().getAsInt();
4

2 に答える 2

23

あなたのコードに基づいて、簡単で汚いマイクロ ベンチマークを作成しました。結果は次のとおりです。

ループ: 3192
ラムダ: 3140
ラムダ 並列: 868

したがって、ループとラムダは同等であり、並列ストリームによりパフォーマンスが大幅に向上します。ベンチマーク方法論が原因で、結果が信頼できないと思われます。

public static void main(String[] args) {
    int sum = 0;

    //warmup
    for (int i = 0; i < 100; i++) {
        solve();
        solveLambda();
        solveLambdaParallel();
    }

    {
        long start = System.nanoTime();
        for (int i = 0; i < 100; i++) {
            sum += solve();
        }
        long end = System.nanoTime();
        System.out.println("loop: " + (end - start) / 1_000_000);
    }
    {
        long start = System.nanoTime();
        for (int i = 0; i < 100; i++) {
            sum += solveLambda();
        }
        long end = System.nanoTime();
        System.out.println("lambda: " + (end - start) / 1_000_000);
    }
    {
        long start = System.nanoTime();
        for (int i = 0; i < 100; i++) {
            sum += solveLambdaParallel();
        }
        long end = System.nanoTime();
        System.out.println("lambda parallel : " + (end - start) / 1_000_000);
    }
    System.out.println(sum);
}

public static int digitSum(BigInteger x) {
    int sum = 0;
    for (char c : x.toString().toCharArray()) {
        sum += Integer.valueOf(c + "");
    }
    return sum;
}

public static int solve() {
    int max = 0;
    for (int i = 1; i < 100; i++) {
        for (int j = 1; j < 100; j++) {
            max = Math.max(max, digitSum(BigInteger.valueOf(i).pow(j)));
        }
    }
    return max;
}

public static int solveLambda() {
    return  IntStream.range(1, 100)
            .map(i -> IntStream.range(1, 100).map(j -> digitSum(BigInteger.valueOf(i).pow(j))).max().getAsInt())
            .max().getAsInt();
}

public static int solveLambdaParallel() {
    return  IntStream.range(1, 100)
            .parallel()
            .map(i -> IntStream.range(1, 100).map(j -> digitSum(BigInteger.valueOf(i).pow(j))).max().getAsInt())
            .max().getAsInt();
}

また、手動テストよりも信頼性の高い jmh で実行しました。結果は上記と一致しています (呼び出しごとのマイクロ秒):

Benchmark                                Mode   Mean        Units
c.a.p.SO21968918.solve                   avgt   32367.592   us/op
c.a.p.SO21968918.solveLambda             avgt   31423.123   us/op
c.a.p.SO21968918.solveLambdaParallel     avgt   8125.600    us/op
于 2014-02-23T13:50:43.077 に答える