as I am training myself with Euler project, I've hardly found a way to resolve problem #5 with Functional approach.
The objective is to find a number that is divisible by all integer numbers from 2 to 20.
I first resolved it with classical Java (I know my code is not nice one and am sorry for that) then I wanted to obtain the result with FP, thinking efficienceness would be greater.
Plain old java took 750 ms to find the result. Stream / FP took around 750 ms.
Have you any ideas / explanations about why FP way need so much time to complete ? I guess my code is not the nicer one, neither plain old java one nor FP one.
But I'd like to understand where I certainly made something wrong.
Notice that parallelizing Stream processing gains about 130 ms (750 ms -> 620 ms).
Notice 2 : it would be nice to start from 9699690L
(that is : 2*3*5*7*9*11*13*17*19
), but it seems to be very looooonger for the app (for both Plain Old Java and FP way) to start... Why ??
Here is the Plain Old Java code :
@Test
void test() {
long start = System.currentTimeMillis();
boolean foundValue = false;
long valueToFindOut = 20L;
List<Long> divisors = Arrays.asList(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L,
19L, 20L);
while (!foundValue) {
boolean found = false;
for (long div : divisors) {
if (isDivisible(valueToFindOut, div)) {
found = true;
} else {
found = false;
break;
}
}
if (!found) {
valueToFindOut += 20L;
} else {
foundValue = true;
System.out.println("Valeur trouvée = " + valueToFindOut);
}
}
for (long div : divisors) {
assertTrue(isDivisible(valueToFindOut, div));
}
long end = System.currentTimeMillis();
System.out.println("Résultat obtenu en " + (end - start) + " millisecondes");
}
private boolean isDivisible(long toDivide, long divisor) {
return toDivide % divisor == 0;
}
Functional code is the following :
@Test
void testLambda() {
long start = System.currentTimeMillis();
List<Long> divisors = Arrays.asList(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L,
19L, 20L);
Predicate<Long> predicate = longPredicate(divisors);
long result = generateLongStream().filter(predicate).findFirst().get();
long end = System.currentTimeMillis();
System.out.println("Resultat = " + result + " obtenu en " + (end - start) + " millisecondes.");
}
private boolean isDivisible(long toDivide, long divisor) {
return toDivide % divisor == 0;
}
private Stream<Long> generateLongStream() {
return Stream.iterate(20L, l -> l + 20L).parallel();
}
private Predicate<Long> longPredicate(List<Long> longs) {
long start = System.currentTimeMillis();
Predicate<Long> predicate = null;
if(!(longs.isEmpty())) {
List<Predicate<Long>> predicates = new ArrayList<Predicate<Long>>(longs.size());
longs.forEach(divisor -> {
predicates.add(valueToTest -> isDivisible(valueToTest, divisor));
});
for(int i = 0; i < predicates.size(); i++) {
if(i == 0) {
predicate = predicates.get(i);
} else {
predicate = predicate.and(predicates.get(i));
}
}
}
long end = System.currentTimeMillis();
System.out.println("Predicate construit en " + (end - start) + " millisecondes.");
return predicate;
}
Thanx by advance for all your advices.
We can replace looping over a list of things...
for( Thing thing : things ){
process(thing);
}
...with something more functional....
things.forEach( thing -> process( thing ) );
...but what actually happens is very similar: we have to iterate over the list calling the process
method for each element of the list. The functional version might even be slightly slower because there's an extra method call to the lambda before the call to the useful method.
So I don't think it's a surprise that the functional version takes a similar time to the original.
The advantage of the functional version might be
But none of those will help the performance.
I will first begin by answering my comment: you should avoid Long
and favor long when dealing with long
based algorithm.
You must never ignore the cost of (un)boxing operation: I rewrote your code using long
, long[]
, LongPredicate
and LongStream
, and on my Ryzen 7 2700X it took the following:
┌─────────────────┬───────┐
│ Method │ Time │
├─────────────────┼───────┤
│ LongStream │ 129ms │
│ Old Java (Long) │ 336ms │
│ Old Java (long) │ 273ms │
└─────────────────┴───────┘
The implementation is given below (sorry if it is too long I don't know how to attach file to Stackoverflow and I don't think pastebin will do).
The LongStream
method is the winner here, but I think the benchmark is wrong:
System::currentTimeMillis
. This method return the current time, which may change (let's say the clock was adjusted due to NTP). You must use System::nanoTime
. The longPredicate
method is doing too much work:
List<Predicate>
to then convert it to one single Predicate
. predicate = predicate.and(...)
. Note that I had to use a method return a lambda, since you would not be able to reference the i
:
for (int i = 1; i < longs.length; ++i) {
predicate = predicate.and(n -> isDisivisble(n, longs[i])); // fail because i is not final
}
You could also create a new local variable (this is done with a parameter in my method.)
So, here the result given by JMH:
┌─────────────────────────────────┬────────┬───────┬───────────┬───────────┬────────┐
│ Benchmark │ Mode │ Cnt │ Score │ Error │ Units │
├─────────────────────────────────┼────────┼───────┼───────────┼───────────┼────────┤
│ PlainOldJavaLongPrimitive.test │ avgt │ 10 │ 188,072 │ 1,002 │ ms/op │
│ PlainOldJavaLongWrapper.test │ avgt │ 10 │ 265,649 │ 0,920 │ ms/op │
│ StreamLongPrimitive.testLambda │ avgt │ 10 │ 86,046 │ 1,829 │ ms/op │
│ StreamLongWrapper.testLambda │ avgt │ 10 │ 230,158 │ 34,122 │ ms/op │
│ PlainOldJavaLongPrimitive.test │ ss │ 10 │ 198,192 │ 37,573 │ ms/op │
│ PlainOldJavaLongWrapper.test │ ss │ 10 │ 268,587 │ 7,378 │ ms/op │
│ StreamLongPrimitive.testLambda │ ss │ 10 │ 116,108 │ 65,161 │ ms/op │
│ StreamLongWrapper.testLambda │ ss │ 10 │ 532,534 │ 335,032 │ ms/op │
└─────────────────────────────────┴────────┴───────┴───────────┴───────────┴────────┘
--
To run this JMH project:
mvn install
java -jar target/microbenchmarks.jar -rff bench
The files:
/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackoverflow.nodatafound</groupId>
<artifactId>stackoverflow-56622173</artifactId>
<version>1</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M2</version>
<executions>
<execution>
<id>enforce-maven</id>
<inherited>true</inherited>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.6.1,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[11.0.0,)</version>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/services/javax.annotation.processing.Processor</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
/src/main/java/com/stackoverflow/nodatafound/q56622173/PlainOldJavaLongPrimitive.java
package com.stackoverflow.nodatafound.q56622173;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Fork(1)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode({ Mode.AverageTime, Mode.SingleShotTime })
public class PlainOldJavaLongPrimitive {
@Benchmark
public Object test() {
boolean foundValue = false;
long valueToFindOut = 20L;
final long[] divisors = new long[] { 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L,
19L, 20L };
while (!foundValue) {
boolean found = false;
for (final long div : divisors) {
if (isDivisible(valueToFindOut, div)) {
found = true;
} else {
found = false;
break;
}
}
if (!found) {
valueToFindOut += 20L;
} else {
foundValue = true;
}
}
for (final long div : divisors) {
if (!isDivisible(valueToFindOut, div)) {
throw new AssertionError("valueToFindOut: " + valueToFindOut + ", div: " + div);
}
}
return Long.valueOf(valueToFindOut);
}
private boolean isDivisible(final long toDivide, final long divisor) {
return toDivide % divisor == 0;
}
}
/src/main/java/com/stackoverflow/nodatafound/q56622173/PlainOldJavaLongWrapper.java
package com.stackoverflow.nodatafound.q56622173;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Fork(1)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode({ Mode.AverageTime, Mode.SingleShotTime })
public class PlainOldJavaLongWrapper {
@Benchmark
public Object test() {
boolean foundValue = false;
long valueToFindOut = 20L;
final List<Long> divisors = Arrays.asList(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L,
18L, 19L, 20L);
while (!foundValue) {
boolean found = false;
for (final long div : divisors) {
if (isDivisible(valueToFindOut, div)) {
found = true;
} else {
found = false;
break;
}
}
if (!found) {
valueToFindOut += 20L;
} else {
foundValue = true;
}
}
for (final long div : divisors) {
if (!isDivisible(valueToFindOut, div)) {
throw new AssertionError("valueToFindOut: " + valueToFindOut + ", div: " + div);
}
}
return Long.valueOf(valueToFindOut);
}
private boolean isDivisible(final long toDivide, final long divisor) {
return toDivide % divisor == 0;
}
}
/src/main/java/com/stackoverflow/nodatafound/q56622173/StreamLongPrimitive.java
package com.stackoverflow.nodatafound.q56622173;
import java.util.concurrent.TimeUnit;
import java.util.function.LongPredicate;
import java.util.stream.LongStream;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Fork(1)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode({ Mode.AverageTime, Mode.SingleShotTime })
public class StreamLongPrimitive {
@Benchmark
public Object testLambda() {
final long[] divisors = new long[] { 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L,
19L, 20L };
final LongPredicate predicate = longPredicate(divisors);
return generateLongStream().filter(predicate).findFirst().getAsLong();
}
private LongPredicate divisiblePredicate(final long divisor) {
return n -> n % divisor == 0;
}
private LongStream generateLongStream() {
return LongStream.iterate(20L, l -> l + 20L).parallel();
}
private LongPredicate longPredicate(final long[] longs) {
if (longs.length == 0) {
throw new IllegalArgumentException("Pas de diviseurs");
}
LongPredicate predicate = divisiblePredicate(longs[0]);
for (int i = 1; i < longs.length; ++i) {
predicate = predicate.and(divisiblePredicate(longs[i]));
}
return predicate;
}
}
/src/main/java/com/stackoverflow/nodatafound/q56622173/StreamLongWrapper.java
package com.stackoverflow.nodatafound.q56622173;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Fork(1)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode({ Mode.AverageTime, Mode.SingleShotTime })
public class StreamLongWrapper {
@Benchmark
public Object testLambda() {
final List<Long> divisors = Arrays.asList(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L,
18L, 19L, 20L);
final Predicate<Long> predicate = longPredicate(divisors);
return generateLongStream().filter(predicate).findFirst().get();
}
private boolean isDivisible(final long toDivide, final long divisor) {
return toDivide % divisor == 0;
}
private Stream<Long> generateLongStream() {
return Stream.iterate(20L, l -> l + 20L).parallel();
}
private Predicate<Long> longPredicate(final List<Long> longs) {
if (longs.isEmpty()) {
throw new IllegalArgumentException("Pas de diviseurs");
}
final List<Predicate<Long>> predicates = new ArrayList<>(longs.size());
longs.forEach(divisor -> {
predicates.add(valueToTest -> isDivisible(valueToTest, divisor));
});
Predicate<Long> predicate = predicates.get(0);
for (int i = 1; i < predicates.size(); i++) {
predicate = predicate.and(predicates.get(i));
}
return predicate;
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.