Java 8并行处理与Lambda表达式

Java 8并行处理与Lambda表达式,java,lambda,parallel-processing,java-8,Java,Lambda,Parallel Processing,Java 8,我想测试Java8并行流的速度有多快,所以我写了一个程序。该程序计算给定数字列表中的素数。程序通过以下方式计算素数: 使用for循环; 使用lambda表达式; 通过使用lambda表达式并行流。 在执行程序之前,我希望并行流版本应该更快。但结果是 4237毫秒内未找到总质数664579——用于循环版本 2440毫秒内未找到总质数664579——并行流 2166毫秒内未找到总质数664579——λ表达式 我的疑问是为什么并行版本比lambda版本慢 上面的程序使用google Guava库来计算

我想测试Java8并行流的速度有多快,所以我写了一个程序。该程序计算给定数字列表中的素数。程序通过以下方式计算素数:

使用for循环; 使用lambda表达式; 通过使用lambda表达式并行流。 在执行程序之前,我希望并行流版本应该更快。但结果是


4237毫秒内未找到总质数664579——用于循环版本
2440毫秒内未找到总质数664579——并行流
2166毫秒内未找到总质数664579——λ表达式

我的疑问是为什么并行版本比lambda版本慢


上面的程序使用google Guava库来计算经过的时间。

最有可能的问题是,在测试期间,JIT编译器重新编译代码。这意味着您的比较是不公平的,因为后面的测试得益于前面的测试所导致的编译

这是微观基准测试中经常犯的错误。有很多文章解释这个问题,例如。如果我先添加一些代码来预热JIT,结果是可以预期的。我的主要方法如下:

public static void main(String... args) {
    System.out.println("Warmup...");
    for (int i = 0; i < 5000; ++i) {
        run(Demo::testLoop, 5000);
        run(Demo::testStream, 5000);
        run(Demo::testParallel, 5000);
    }
    System.out.println("Benchmark...");
    int bound = 10000000;
    System.out.printf("Loop:     %s\n", run(Demo::testLoop, bound));
    System.out.printf("Stream:   %s\n", run(Demo::testStream, bound));
    System.out.printf("Parallel: %s\n", run(Demo::testParallel, bound));
}
Loop:     7.06s (664579)
Stream:   7.06s (664579)
Parallel: 3.84s (664579)
如果您将选项-XX:+printcomilation传递给Java VM,您可以看到JIT何时何地开始,并且现在几乎所有编译都发生在预热阶段

请注意,并行流不是这种并行化的最佳解决方案,因为iPrime的复杂性取决于该值。这意味着,序列的前半部分比后半部分所需的工作量要少得多,以此类推

以下是我的其余实现方法,以供参考:

public static boolean isPrime(int value) {
    for (int i = 2; i * i <= value; ++i)
        if (value % i == 0) return false;
    return true;
}

public static long testLoop(int bound) {
    long count = 0;
    for (int i = 2; i < bound; ++i)
        if (isPrime(i)) ++count;
    return count;
}

public static long testStream(int bound) {
    return IntStream.range(2, bound).filter(Demo::isPrime).count();
}

public static long testParallel(int bound) {
    return IntStream.range(2, bound).parallel().filter(Demo::isPrime).count();
}

public static String run(IntToLongFunction operation, int bound) {
    long start = System.currentTimeMillis();
    long count = operation.applyAsLong(bound);
    long millis = System.currentTimeMillis() - start;
    return String.format("%4.2fs (%d)", millis / 1000.0, count);
}

最有可能的问题是,在测试期间,JIT编译器会重新编译代码。这意味着您的比较是不公平的,因为后面的测试得益于前面的测试所导致的编译

这是微观基准测试中经常犯的错误。有很多文章解释这个问题,例如。如果我先添加一些代码来预热JIT,结果是可以预期的。我的主要方法如下:

public static void main(String... args) {
    System.out.println("Warmup...");
    for (int i = 0; i < 5000; ++i) {
        run(Demo::testLoop, 5000);
        run(Demo::testStream, 5000);
        run(Demo::testParallel, 5000);
    }
    System.out.println("Benchmark...");
    int bound = 10000000;
    System.out.printf("Loop:     %s\n", run(Demo::testLoop, bound));
    System.out.printf("Stream:   %s\n", run(Demo::testStream, bound));
    System.out.printf("Parallel: %s\n", run(Demo::testParallel, bound));
}
Loop:     7.06s (664579)
Stream:   7.06s (664579)
Parallel: 3.84s (664579)
如果您将选项-XX:+printcomilation传递给Java VM,您可以看到JIT何时何地开始,并且现在几乎所有编译都发生在预热阶段

请注意,并行流不是这种并行化的最佳解决方案,因为iPrime的复杂性取决于该值。这意味着,序列的前半部分比后半部分所需的工作量要少得多,以此类推

以下是我的其余实现方法,以供参考:

public static boolean isPrime(int value) {
    for (int i = 2; i * i <= value; ++i)
        if (value % i == 0) return false;
    return true;
}

public static long testLoop(int bound) {
    long count = 0;
    for (int i = 2; i < bound; ++i)
        if (isPrime(i)) ++count;
    return count;
}

public static long testStream(int bound) {
    return IntStream.range(2, bound).filter(Demo::isPrime).count();
}

public static long testParallel(int bound) {
    return IntStream.range(2, bound).parallel().filter(Demo::isPrime).count();
}

public static String run(IntToLongFunction operation, int bound) {
    long start = System.currentTimeMillis();
    long count = operation.applyAsLong(bound);
    long millis = System.currentTimeMillis() - start;
    return String.format("%4.2fs (%d)", millis / 1000.0, count);
}

众所周知,F/J框架需要预热。代码的编写方式使得它只有在编译时才能很好地执行。您还必须考虑创建线程所需的时间。在生产环境中进行预热是主观的


框架中有很多糟糕的代码,可以在第一次启动时克服这种缓慢的行为

众所周知,F/J框架需要预热。代码的编写方式使得它只有在编译时才能很好地执行。您还必须考虑创建线程所需的时间。在生产环境中进行预热是主观的


框架中有很多糟糕的代码,可以在第一次启动时克服这种缓慢的行为

在进行基准测试时,您还应注意预热周期:count1和count2是相同的…我更改了它,但结果不一致4178毫秒中未找到总质数664579-循环2597毫秒中未找到总质数664579-并行流4187毫秒中未找到总质数664579-lambda表达式总质数否在4198毫秒内找到664579-循环总质数在4690毫秒内没有找到664579-在并行流中总质数在4195毫秒内没有找到664579-在lambda表达式中总质数在4252毫秒内没有找到664579-循环总质数在2455毫秒内没有找到664579-并行总质数在4555毫秒内没有找到664579-lambda表达式您的基准测试方法有点过于简单。您应该使用一个框架,如jmh。在进行基准测试时,您还应该注意预热周期:count1和count2是相同的…我更改了它,但结果不一致4178毫秒内未找到总质数664579-2597毫秒内未找到循环总质数664579-4187毫秒内未找到并行流总质数664579-lambda表达式总质数在4198毫秒内未找到664579-循环总质数在4690毫秒内未找到664579-并行流中总质数在4195毫秒内未找到664579-lambda表达式中总质数在4252毫秒内未找到664579-循环总质数在2455毫秒内未找到664579-并行总质数在4555中未找到664579mili sec-lambda Expression您的基准测试方法有点过于简单。Y
你应该使用一个框架,比如jmh。你能详细说明什么是JIT预热吗?如果我没记错的话,你是通过for循环在side main方法中进行预热的。谢谢你的详细解释我添加了一个链接到一篇解释热身的文章。您可以使用命令行选项-XX:+printcomilation来查看发生了什么。您可以详细说明什么是JIT预热吗?如果我没记错的话,您正在通过for循环在side main方法中进行预热。谢谢你的详细解释我添加了一个链接到一篇解释热身的文章。您可以使用命令行选项-XX:+printcomilation查看发生了什么。