[英]How to build a more efficient functional code ? Java FP
as I am training myself with Euler project, I've hardly found a way to resolve problem #5 with Functional approach. 当我在Euler项目上训练自己时,我几乎没有找到一种使用功能性方法解决问题5的方法。
The objective is to find a number that is divisible by all integer numbers from 2 to 20. 目的是找到可被2到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. 我首先用经典Java解决了这一问题(我知道我的代码不好用,对此感到抱歉),然后我想用FP获得结果,认为效率会更高。
Plain old java took 750 ms to find the result. 普通的旧Java花费了750毫秒才能找到结果。 Stream / FP took around 750 ms. 流/ FP大约花费了750毫秒。
Have you any ideas / explanations about why FP way need so much time to complete ? 关于FP方式为什么需要这么多时间来完成,您是否有任何想法/解释? I guess my code is not the nicer one, neither plain old java one nor FP one. 我猜我的代码不是更好的代码,不是普通的旧Java代码还是FP代码。
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). 请注意,并行化Stream处理可获得约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 ?? 注意2:从9699690L
(即: 2*3*5*7*9*11*13*17*19
)开始很好,但对于该应用程序来说似乎非常宽松(对于这两个Plain Old Java而言)和FP方式)开始...为什么?
Here is the Plain Old Java code : 这是普通的旧Java代码:
@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. ...但是实际发生的情况非常相似:我们必须遍历列表,为列表的每个元素调用process
方法。 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. 功能版本甚至可能会稍微慢一些,因为在调用有用方法之前,有一个额外的方法调用lambda。
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. 首先,我将回答我的评论:在处理基于long
算法的算法时,应避免使用Long
并偏爱long。
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: 您绝对不能忽略(取消)装箱操作的成本:我使用long
, long[]
, LongPredicate
和LongStream
重写了您的代码,在我的Ryzen 7 2700X上它花费了以下时间:
┌─────────────────┬───────┐
│ 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). 下面给出了实现(很抱歉,如果时间太长,我不知道如何将文件附加到Stackoverflow,并且我认为pastebin也不会这样做)。
The LongStream
method is the winner here, but I think the benchmark is wrong: LongStream
方法是这里的赢家,但是我认为基准是错误的:
System::currentTimeMillis
. 您绝不能使用System::currentTimeMillis
替补。 This method return the current time, which may change (let's say the clock was adjusted due to NTP). 此方法返回当前时间,该时间可能会更改(例如,由于NTP调整了时钟)。 You must use System::nanoTime
. 您必须使用System::nanoTime
。 The longPredicate
method is doing too much work: longPredicate
方法做了太多的工作:
List<Predicate>
to then convert it to one single Predicate
. 您无需转换List<Predicate>
即可将其转换为一个Predicate
。 predicate = predicate.and(...)
. 您可以简单地初始化初始谓词,然后调用predicate = predicate.and(...)
。 Note that I had to use a method return a lambda, since you would not be able to reference the i
: 请注意,由于您将无法引用i
,因此我必须使用返回lambda的方法:
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: 因此,这是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: 要运行此JMH项目:
mvn install
编译项目: mvn install
java -jar target/microbenchmarks.jar -rff bench
运行基准测试: java -jar target/microbenchmarks.jar -rff bench
The files: 文件:
/pom.xml /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 /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 /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 /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 /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;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.