Не могли бы вы дать мне ссылку, почему существует существенная разница во времени выполнения между следующими 2 факториальными реализациями, использующими Java Stream API:
- Последовательная реализация
- Параллельная реализация (использование Stream.parallel ()), выполняемого в пользовательском пуле объединения вилок с параллелизмом, установленным на 1
Мои ожидания заключались в том, чтобы иметь близкое время выполнения, однако параллельная версия имеет ускорение в 2 раза.Я не запускал никаких специализированных тестов, однако время выполнения не должно сильно отличаться даже при холодном запуске jvm.Ниже я прикрепляю исходный код двух реализаций:
public class FastFactorialSupplier implements FactorialSupplier {
private final ExecutorService executorService;
public FastFactorialSupplier(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public BigInteger get(long k) {
try {
return executorService
.submit(
() -> LongStream.range(2, k + 1)
.parallel()
.mapToObj(BigInteger::valueOf)
.reduce(BigInteger.ONE, (current, factSoFar) -> factSoFar.multiply(current))
)
.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return BigInteger.ZERO;
}
}
public class MathUtils {
public static BigInteger factorial(long k) {
return LongStream.range(2, k + 1)
.mapToObj(BigInteger::valueOf)
.reduce(BigInteger.ONE, (current, factSoFar) -> factSoFar.multiply(current));
}
}
Вот тестовые примеры с приложенным временем выполнения примера в виде комментариев, основанных на том, что показал бегунок intellij junit.
@Test
public void testWithoutParallel() {
//2s 403
runTest(new DummyFactorialSupplier()); // uses MathUtils.factorial
}
@Test
public void testParallelismWorkStealing1() {
//1s 43
runTest(new FastFactorialSupplier(Executors.newWorkStealingPool(1)));
}
@Test
public void testParallelismForkJoin1() {
// 711ms
runTest(new FastFactorialSupplier(new ForkJoinPool(1)));
}
@Test
public void testExecutorForkJoin() {
//85ms
runTest(new FastFactorialSupplier(new ForkJoinPool()));
}
private void runTest(FactorialSupplier factorialSupplier) {
BigInteger result = factorialSupplier.get(100000);
assertNotNull(result);
// assertEquals(456574, result.toString().length());
}
Тесты были выполнены с использованием java 11, так какбыла проблема в java 8 с пользовательскими пулами объединения вилок - https://bugs.openjdk.java.net/browse/JDK-8190974
Может ли быть оптимизация, связанная с псевдопараллельной обработкой и с тем, как запланировано выполнение, тогда как такого нет, если выполнение чисто последовательное?
Редактировать:
Я также запускаю микробенчмарк, используя jmh
Параллельно:
public class FastFactorialSupplierP1Test {
@Benchmark
@BenchmarkMode({Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime, Mode.Throughput, Mode.All})
@Fork(value = 1, warmups = 1)
public void measure() {
runTest(new FastFactorialSupplier(new ForkJoinPool(1)));
}
private void runTest(FactorialSupplier factorialSupplier) {
BigInteger result = factorialSupplier.get(100000);
assertNotNull(result);
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
Серийный номер:
public class SerialFactorialSupplierTest {
@Benchmark
@BenchmarkMode({Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime, Mode.Throughput, Mode.All})
@Fork(value = 1, warmups = 1)
public void measure() {
runTest(new DummyFactorialSupplier());
}
private void runTest(FactorialSupplier factorialSupplier) {
BigInteger result = factorialSupplier.get(100000);
assertNotNull(result);
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
public class IterativeFactorialTest {
@Benchmark
@BenchmarkMode({Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime, Mode.Throughput, Mode.All})
@Fork(value = 1, warmups = 1)
public void measure() {
runTest(new IterativeFact());
}
private void runTest(FactorialSupplier factorialSupplier) {
BigInteger result = factorialSupplier.get(100000);
assertNotNull(result);
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
class IterativeFact implements FactorialSupplier {
@Override
public BigInteger get(long k) {
BigInteger result = BigInteger.ONE;
while (k-- != 0) {
result = result.multiply(BigInteger.valueOf(k));
}
return result;
}
}
}
Результаты:
FastFactorialSupplierP1Test.measure avgt 5 0.437 ± 0.006 s/op
IterativeFactorialTest.measure avgt 5 2.643 ± 0.383 s/op
SerialFactorialSupplierTest.measure avgt 5 2.226 ± 0.044 s/op