Based on recent discussions here, I wanted to take a broader look at the question of who eats more - new-fashioned hipster lambdas or old proven anonymous classes. Let's arrange a verbal skirmish between them and see who will win. As with any good holivar, even if you can not find out the winner, you can learn a lot of new things for yourself.
Lambda-kun : heh. Hehe hehe. No, this is not serious. Well, how can this be compared to poverty:
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello!"); } };
With this beauty:
Runnable r = () -> System.out.println("Hello!");
Anon-san : well, well, young man, there is no need to say so. Nowadays, nobody cares about what is actually in the file. It is enough to take a good IDE and the difference is almost imperceptible.
Here is the anonymous class:
But lambda:
Anon-san : at the same time, you can turn me around and see what I really am, but what you really are is a big mystery.
Lambda-kun : hh-hm ... No, that, of course, is not fair. Okay. But on the disk, I take less. Take a simple class:
public class Test { Runnable r = () -> {}; }
And with you there will be this:
public class Test { Runnable r = new Runnable() { @Override public void run() { } }; }
52 bytes to 126! What is it, huh?
Anon-san : well, I agree with the source bytes, although whom they care about. And if you compile?
Lambda-kun : Naturally, I will win! From me one file will turn out, and from you in general two! In two files, twice as many headers and any meta-information.
Anon-san : do not hurry, young man, let's check. Run javac Test.java
for both versions. What do we see? The anonymous class version generates Test.class
(308 bytes) and Test$1.class
(377 bytes), a total of 685 bytes, and the lambda option only generates Test.class
, but it weighs 783 bytes. Almost a hundred bytes overhead - not expensive for syntactic sugar?
Lambda-kun : uh, how did that come about? It could not be, I'm lighter! Well, with debug information, how much will it be? Still compile with it.
Anon-san : let's try: javac -g Test.java
. Lambda - 838 bytes, an anonymous class - 825 bytes. So the difference is smaller, but still the vaunted lightness is not visible. You forget that every lambda call creates a spreading entry in Bootsrtap methods , which anonymous classes do not need.
Lambda-kun : wait, wait. I got it. This is not the recording itself spreading, but constants that fall into the pool of constants. Anything like java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
. Yes, they are long, but reused, if the lambda is more than one. Well, add the second:
public class Test { Runnable r = () -> {}; Runnable r2 = () -> {}; }
Lambda-kun : Vooot, I'm already winning! With debug information 957 bytes, and if you replace with anonymous classes, it will be important to 1351 bytes in three files. Even without debug information, I win. But other constants may effectively be reused! Any fields, methods, classes used inside lambda. If they are used in several lambdas or in lambda and around it, they will collapse into one constant. And with anonymous classes in each will be a copy. That's the same thing!
Anon-san : apparently, then I have to give up. If lambda is a lot, then you are really smaller in compiled form. However, it is more interesting what happens in runtime, in the memory of the virtual machine. Suppose you do not have an anonymous class on the disk, but the exact same class, or even a larger one, will be generated at startup and devour all the same resources.
Lambda-kun : but not the same! I'm lightweight! There will surely be generated a small compact classic that does not contain any unnecessary nonsense. And how will you check it? Well it is all in runtime in memory!
Anon-san : but you are ashamed of not knowing this. Should you somehow debug the developers. There is an undocumented system property jdk.internal.lambda.dumpProxyClasses
, with which you can specify in which directory to throw the generated runtime views lambda. Run the application with -Djdk.internal.lambda.dumpProxyClasses=.
and see everything.
Lambda-kun : aha, only as long as you never use lambda, the runtime presentation will not be generated at all, and anonymous classes always exist, even if they have never been useful!
Anon-san : there is no difference. On the contrary, the difference is not in your favor. An anonymous class always exists on disk, but it will not be loaded into memory until it is used. Of course, the Rantaim representation of lambda will not be generated, but its body is actually loaded in the form of a private synthetic method along with the class in which it is declared. Even if the body is never used, it will remove the memory. However, we will return to this issue later. Let's see first what happens if lambda is used. To do this, we need to modify the program a bit:
public class Test { static Runnable r = () -> {}; public static void main(String[] args) { } }
Compile (well, even with debug information), run java -Djdk.internal.lambda.dumpProxyClasses=. Test
java -Djdk.internal.lambda.dumpProxyClasses=. Test
and see: lambda created the class Test$$Lambda$1.class
, which weighs 308 bytes. This is in addition to the main class Test.class
, which weighs 1004 bytes. Replacing lambda with a similar anonymous class, we have 508 + 399 bytes in two classes at once, but nothing is created in runtime. You still eat a lot, young man, 405 bytes more than me.
Lambda-kun : Well, we agreed that it would be dishonest to measure with one lambda. Let's add the second one.
Anon-san : yes, at least ten. We add static Runnable r1 = () -> {};
and so on. It turns out 11 classes, with lambdas 5174 bytes, and with anonymous - 5059 bytes. Slowly I’m catching up with you, of course, but, you see, I don’t have 10 lambdas in every class. Somewhere after the 14th anonymous class, only you start to eat less.
Lambda-kun : well, well. And let's put all these lambdas right into the main()
method. Admit it, they rarely lie in static fields, ne?
public class Test { public static void main(String[] args) { Runnable r = new Runnable() {public void run() {}}; Runnable r1 = new Runnable() {public void run() {}}; Runnable r2 = new Runnable() {public void run() {}}; ... } }
Anon-san : hmm, what's the difference?
Lambda-kun : compile and see. At me just there is no difference, the classes generated in rantayma weigh as much. And you have every byte 40 fat. Now I eat less than ten lambdas (5,290 bytes versus 4,995). Already even at six, I'm ahead of you!
Anon-san : ah, there it is. For debugging, a line EnclosingMethod: Test.main
now been added to each anonymous class, which, of course, eats up additional space. Eh, in vain I agreed to debug information.
Lambda-kun : this entry is added even with debugging information completely disabled ( javac -g:none
). This attribute must be specified by the specification, regardless of debugging. And my runtime representation is not formally an anonymous class, and it does not need this attribute. You're lucky that the name of the main
method is so short. If anonymous classes in a method with a long name, each will eat off additionally in proportion to its length!
Anon-san : in my opinion, our game is already flowing into a dishonest plane. So here, with your permission, the counterstrike: closure on variables. I accept your condition and stay inside the method. But let's capture the variable:
import java.util.function.IntSupplier; public class Test { public static void main(String[] args) { int i = 0; IntSupplier s = () -> i; IntSupplier s1 = () -> i; IntSupplier s2 = () -> i; ... } }
Well, for anonymous classes, replace with new IntSupplier() {public int getAsInt() {return i;}}
. How much do you think lambdas will take to defeat anonymous classes now?
Lambda-kun : well, there should be no difference. Your compiler generates a synthetic field and a constructor with one parameter that initializes this field. At me approximately the same will be created in. Some sort of such a class is generated for both you and me:
class Test$1 implements IntSupplier { private final int val$i; Test$1(int i) { val$i = i; } @Override int getAsInt() { return val$i; } }
Anon-san : so, but not so. We try. One lambda: 1493 bytes, one anonymous class: 1006 bytes. Ten lambda: 6803 bytes, ten anonymous classes: 6039 bytes. Twenty lambda: 12743 bytes, twenty anonymous classes: 11669 bytes. The gap is constantly increasing! There are at least a thousand lambdas, and you will not catch up with me.
Lambda-kun : uh ... So. Well, we decompile. What kind of nonsense is this? Any factory method? Some kind of nonsense. In addition to the constructor, I also for some reason add a method of the form static IntSupplier get$Lambda(int i) { return new Test$1(i);}
. What nonsense, why?
Anon-san : not nonsense, but performance. Sometime in ancient times, Walrus corrected the speed of instantiating lambda in the interpreter ( JDK-8023984 ). The factory method was faster than the constructor. Notice, young man, there are no such strange problems with me, everything is fast and so.
Lambda-kun : this is nonsense! There is no to finish their metotekhandly to the mind, they sculpt crutches ... Interestingly, maybe you have finished it since then and this method has not become necessary? ..
Anon-san : how to know, how to know ...
Lambda-kun : but my turn! I accept your condition and variable capture, but let's not IntSupplier
, but Supplier<Integer>
:
import java.util.function.Supplier; public class Test { public static void main(String[] args) { int i = 0; Supplier<Integer> s = new Supplier<Integer>() {public Integer get() {return i;}}; Supplier<Integer> s1 = new Supplier<Integer>() {public Integer get() {return i;}}; ... } }
Well, the lambda will remain as before: Supplier<Integer> s = () -> i
.
Anon-san : hmm ... I don’t see where you want to fool me ... And, yes, a Signature: Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
entry will be added Signature: Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
This is so that every reflection works correctly and s.getClass().getGenericInterfaces()
returns the Supplier<Integer>
, and not just the Supplier
. Do not you need it?
Lambda-kun : it turns out not. Lambda is permissible for it not to work for her!
Anon-san : however, although this line will remove the place, I can not believe that it is very much against your factory method.
Lambda-kun : but you do not believe, but check. Now only three lambdas eat less than three anonymous classes (2963 against 3034 bytes) and with each new line you lose more and more! Each anonymous class eats 270 bytes more than the corresponding lambda. And this is in view of the fact that I have an extra factory method!
Anon-san : can not be. What else is there a compiler? Aah, how could I forget. Bridge method Since we have Integer get() {}
in the code, and Object get()
in the interface after erasure, we need another bridge, which, when the interface is called, will redirect to Integer get()
. Do you need a bridge?
Lambda-kun : no, and we do not need a bridge. More precisely, on the contrary, we always need it, Object get()
is a bridge, and the actual implementation is in the main class in a synthetic method of the form lambda$main$1
. But the bridge is always the same; in the case of generics, the second bridge is not needed. But you needed and then it became clear that we are still actually lightweight!
Anon-san : in general, of course, our tests are not very reliable. It is not known how far the size of the class files and the memory consumption in runtime actually correlate. But for today the conversation has been delayed, so postpone this question for the next time.
Source: https://habr.com/ru/post/313350/
All Articles