Cameron McKenzie has written an article on Java 8's new Lambdas so I thought I'd take a look at the new JDK. It's available here but note you may have IDE problems while the Java community decide on the exact syntax. I had to upgrade my IntelliJ to 12.1.5 to get the new syntax recognised.
McKenzie says that using Lambdas produces more efficient code but in my own tests, I haven't seen this. I took his code, only slightly modified it to remove System.out.println and put it into a Caliper test such that it looked like this:
public int timeLambdas(int reps) {
final Set groups = initGroups(reps);
final List persons = initPersons(groups, reps);
Stream sorted = persons.stream()
.filter(p -> p.getAge() < reps)
.map(p -> p.getGroup())
.distinct()
.sorted(comparing(g -> g.getSize()));
int doNotJit = sorted.map(g -> g.getSize()).reduce(0, (i, j) -> i |= j);
return doNotJit;
}
(Note: I had to map/reduce the data otherwise all the filtering, mapping and dupe-removal does not get called. Lambdas are lazy and the code will not execute until asked to actually do something. I was very confused for an hour as I scratched my head wondering how on Earth they had made it so 'efficient'...).
Similarly, I had to make a slight modification to the traditional way of Java coding:
public int timeTraditional(int reps) {
final Set myGroups = initGroups(reps);
final List persons = initPersons(myGroups, reps);
Set groups = new HashSet<>();
for (Person p : persons) {
if (p.getAge() < reps)
groups.add(p.getGroup());
}
List sorted = new ArrayList<>(groups);
Collections.sort(sorted, new Comparator() {
public int compare(Group a, Group b) {
return Integer.compare(a.getSize(), b.getSize());
}
});
int doNotJit = 1;
for (Group g : sorted) {
doNotJit |= g.getSize();
}
return doNotJit;
}
So, running these in Caliper produced:
0% Scenario{vm=/Users/phenry/Tools/JVMs/jdk1.8.0/bin/java, trial=0, benchmark=Lambdas} 753.72 ns; σ=98.39 ns @ 10 trials
50% Scenario{vm=/Users/phenry/Tools/JVMs/jdk1.8.0/bin/java, trial=0, benchmark=Traditional} 530.16 ns; σ=60.72 ns @ 10 trials
benchmark ns linear runtime
Lambdas 754 ==============================
Traditional 530 =====================
Hmm, that's somewhat disappointing.
With a little bit of effort, I made the traditional approach even more efficient. I don't yet know enough about the new Lambdas to do the same for them but I suspect that there is more autoboxing going on as I mentioned before in other attempts to bring functional programming to the Java language. After all, the reduce method on the Stream interface (the gateway drug to Java 8's functional style) looks like this:
T reduce(T identity, BinaryOperator accumulator);
(Note how that BinaryOperator is mapped to (i, j) -> ... )
That T represents a Class yet in my use of it in reduce(0, (i, j) -> i |= j), it's a a primitive int (0). Maybe, they'll optimise this in the future.
McKenzie says that using Lambdas produces more efficient code but in my own tests, I haven't seen this. I took his code, only slightly modified it to remove System.out.println and put it into a Caliper test such that it looked like this:
public int timeLambdas(int reps) {
final Set
final List
Stream
.filter(p -> p.getAge() < reps)
.map(p -> p.getGroup())
.distinct()
.sorted(comparing(g -> g.getSize()));
int doNotJit = sorted.map(g -> g.getSize()).reduce(0, (i, j) -> i |= j);
return doNotJit;
}
(Note: I had to map/reduce the data otherwise all the filtering, mapping and dupe-removal does not get called. Lambdas are lazy and the code will not execute until asked to actually do something. I was very confused for an hour as I scratched my head wondering how on Earth they had made it so 'efficient'...).
Similarly, I had to make a slight modification to the traditional way of Java coding:
public int timeTraditional(int reps) {
final Set
final List
Set
for (Person p : persons) {
if (p.getAge() < reps)
groups.add(p.getGroup());
}
List
Collections.sort(sorted, new Comparator
public int compare(Group a, Group b) {
return Integer.compare(a.getSize(), b.getSize());
}
});
int doNotJit = 1;
for (Group g : sorted) {
doNotJit |= g.getSize();
}
return doNotJit;
}
So, running these in Caliper produced:
0% Scenario{vm=/Users/phenry/Tools/JVMs/jdk1.8.0/bin/java, trial=0, benchmark=Lambdas} 753.72 ns; σ=98.39 ns @ 10 trials
50% Scenario{vm=/Users/phenry/Tools/JVMs/jdk1.8.0/bin/java, trial=0, benchmark=Traditional} 530.16 ns; σ=60.72 ns @ 10 trials
benchmark ns linear runtime
Lambdas 754 ==============================
Traditional 530 =====================
Hmm, that's somewhat disappointing.
With a little bit of effort, I made the traditional approach even more efficient. I don't yet know enough about the new Lambdas to do the same for them but I suspect that there is more autoboxing going on as I mentioned before in other attempts to bring functional programming to the Java language. After all, the reduce method on the Stream interface (the gateway drug to Java 8's functional style) looks like this:
T reduce(T identity, BinaryOperator
(Note how that BinaryOperator
That T represents a Class yet in my use of it in reduce(0, (i, j) -> i |= j), it's a a primitive int (0). Maybe, they'll optimise this in the future.
No comments:
Post a Comment