-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use invokedynamic for lambdas (behind -Ydelambdafy:method) #8359
Comments
Imported From: https://issues.scala-lang.org/browse/SI-8359?orig=1 |
@adriaanm said: |
@gkossakowski said: Java 8 doesn't have more performant lambdas implementation compared to anonymous classes. With Java 8, you need to load a class per every lambda but the class is generated at runtime. The size of the resulting bytecode is the same as with anonymous classes. I explained those issues in detail, here: http://www.takipiblog.com/2014/01/16/compiling-lambda-expressions-scala-vs-java-8/#comment-1206323508 So the benefits are not that big as they might appear from your description. However, there are still benefits like smaller jars (but the same bytecode at runtime!) that are worth pursuing Java 8-style translation of lambdas. |
Nik (cosmicvisitors) said: |
@gkossakowski said: In the comments I linked to I described the reasons why there can't be any performance difference: Java 8 expands invokedynamc calls to regular anonymous classes generated at runtime. My claim is supported with the link to documentation by Brian Goetz (from Java compiler team) which explains lambda translation in great detail. That's the proof. :) In the second comment I linked I admit that Java 8 is indeed faster when it comes to lambdas that do not close over variables but this optimization is not related to use of invokedynamic. Still, I would be interested in results of your benchmarks. |
Nik (cosmicvisitors) said (edited on Mar 8, 2014 3:21:46 PM UTC): // Here's the Java version @GenerateMicroBenchmark Iteration 1: 129700.223 ops/ms Result : 129943.095 ±(99.9%) 123.534 ops/ms // Here's the Scala version final val testValues = Array("a", "bb", "c", "dd", "e", "ff").toBuffer @GenerateMicroBenchmark Iteration 1: 121176.798 ops/ms Result : 121714.492 ±(99.9%) 504.060 ops/ms So basically it seems the Java version of lambdas executing a simple foreach lambda is a bit faster than Scala's version, when using the defacto mutable list data structure in each language. However this could be attributed to the differences in implementation of mutable lists of each language; it is not conclusive (although most people will realistically see this kind of performance for lists). The best results we could get would be from some data structure which is implemented in the same way in both languages (or is exactly the same) such as an array. On the other hand, Scala does allow me to run a foreach lambda on an array, so I tried this as well, as I tend to use a lot of arrays in my Scala code. // Scala array version final val testValues = Array("a", "bb", "c", "dd", "e", "ff") @GenerateMicroBenchmark Iteration 1: 158810.042 ops/ms Result : 158269.531 ±(99.9%) 260.444 ops/ms So there, Scala lambdas may execute faster than Java's, if you use arrays; not necessarily because of their implementation, but because of their flexibility! |
@adriaanm said: |
Nik (cosmicvisitors) said: I received a comment from Alexey Shipilev (from Oracle), an expert in JMH micro-benchmarking, and he suggested that as the length() integer is not used, there may be some dead code elimination optimization taking place that may skew results. I've now re-run the micro-benchmarks with using the value returned from the lambda, and now it seems the Scala version wins when using lambdas on lists as well. // Java version @State(Scope.Thread) @GenerateMicroBenchmark Iteration 1: 1783.691 ops/ms Result : 1787.097 ±(99.9%) 13.281 ops/ms // Scala version @State(Scope.Thread) final val testValues = Array("a", "bb", "c", "dd", "e", "ff").toBuffer @GenerateMicroBenchmark Iteration 1: 1957.770 ops/ms Result : 1941.962 ±(99.9%) 24.382 ops/ms All the results in this page were ran with JDK 1.8.0 build129, both Java & Scala versions of programs ran on the same JRE version. Hardware: Intel Core i5 3570K (Ivy Bridge) @ 3.4GHz Given these results, I think perhaps the JDK guys will need to spend some time improving the performance of the current invokedynamic implementation, in their next releases. Hopefully if there is some development on the Scala side to use it, as Adriaan has indicated, switching to it (given some potential speed advantage in the future) will be easily possible. Regards, |
@gkossakowski said: |
Nik (cosmicvisitors) said: |
@retronym said (edited on Mar 14, 2014 2:25:52 PM UTC): /*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.sample;
import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.logic.BlackHole;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@State(Scope.Benchmark)
public class MyBenchmark {
private final List<String> testValues = Arrays.asList("a", "bb", "c", "dd", "e", "ff");
@GenerateMicroBenchmark
public void lambda() throws InterruptedException { testValues.forEach(p -> new BlackHole().consume(p.length())); }
@GenerateMicroBenchmark
public void anonClass() throws InterruptedException {
Consumer<String> action = new Consumer<String>() {
public void accept(String x) {
new BlackHole().consume(x.length());
}
};
testValues.forEach(action);
}
}
I suspect the slight win for the lambda is the fact it is statically hoisted, as it doesn't close over any free variables. |
@adriaanm said (edited on Mar 14, 2014 7:05:03 PM UTC): |
@gkossakowski said: |
@retronym said (edited on Mar 14, 2014 9:14:05 PM UTC): |
@retronym said: |
@gkossakowski said: I just recalled this talk which goes pretty deep into lambda performance vs anon classes: http://medianetwork.oracle.com/video/player/2623576348001. Unsurprisingly, there are no simple answers. |
Nik (cosmicvisitors) said: @jason- I don't really think something kicking in would've had a large effect, this benchmark uses a single core. |
Thomas Santana (nebulorum) said: Another comment is that there is likely to be performance improvements in the future on this strategy, specially if Java Lambdas start to be used all over the place and become more relevant. |
@gkossakowski said: |
hepin (hepin1989) said: just a note,you guys could take a look |
@gkossakowski said: Thanks for the link. However, that article is wrong. This comment I've made some time ago: http://www.takipiblog.com/compiling-lambda-expressions-scala-vs-java-8/#comment-1206323508 applies to the Infoq article too. If you are interested in understanding the details check out the talk I've given at SoundCloud some time ago: http://vimeo.com/soundcloud/review/105332268/201bc5a9f7 |
hepin (hepin1989) said: |
@gkossakowski said: |
@retronym said: |
@retronym said: |
Nik (cosmicvisitors) said: |
The implementation of lambdas in Java 8 is more performant[1] than the current implementation of lambdas in Scala. Moreover it is more lightweight in that no synthetic class definitions are emitted; this means fewer classes to classload [2], translating into compilation speed and perm-gen benefits. The generated bytecode is also much shorter [3], allowing for more inlining opportunities. Lastly, as no new anonymous class instance is created per execution (rather an invokedynamic+ execution local class call takes place), this helps with garbage collection (fewer heap allocations). This may also help with cache locality.
A presentation [4] by Vlastimik Mencik claims that Scala 2.12.x will target Java 8 but it's not clear if indy (invokedynamic) will be used or not. So I'm starting this to track progress and potentially get confirmation about whether this is a formal decision, given that Martin Odersky seems to be saying [5] this is not possible without breaking binary compatibility.
In my humble view, the benefits are too great to ignore and this impacts myself and a lot of us who want to continue using functional concepts in high-performance applications.
[1] p32, http://www.slideshare.net/jaxlondon2012/lambda-a-peek-under-the-hood-brian-goetz
[2] p6-7, http://www.slideshare.net/czechscala/java-8-under-the-hood
[3] http://www.javacodegeeks.com/2014/01/compiling-lambda-expressions-scala-vs-java-8.html
[4] p13, http://www.slideshare.net/czechscala/java-8-under-the-hood
[5]
The text was updated successfully, but these errors were encountered: