查看: 5181|回复: 5

Compiling Lambda Expressions: Scala vs. Java 8

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
发表于 2014-1-27 21:22 | 显示全部楼层 |阅读模式

Lambda expressions have taken the programming world by storm in the last few years. Most modern languages have adopted them as a fundamental part of functional programming. JVM based languages such as Scala, Groovy and Clojure have integrated them as key part of the language. And now, Java 8 is (finally) joining in on the fun.

What’s interesting about Lambda expressions is that from the JVM’s perspective they’re completely invisible. It has no notion of what an anonymous function or a Lambda expression is. It only knows bytecode which is a strict OO specification. It’s up to the makers of the language and its compiler to work within these constraints to create newer, more advanced language elements.

To get things going I took a simple Lambda expression that converts a list of Strings to a list of their lengths.

Don’t be tricked its simplicity – behind the scenes some complex stuff is going on.
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
 楼主| 发表于 2014-1-27 21:23 | 显示全部楼层
Lambda expressions have taken the programming world by storm in the last few years. Most modern languages have adopted them as a fundamental part of functional programming. JVM based languages such as Scala, Groovy and Clojure have integrated them as key part of the language. And now, Java 8 is (finally) joining in on the fun.

What’s interesting about Lambda expressions is that from the JVM’s perspective they’re completely invisible. It has no notion of what an anonymous function or a Lambda expression is. It only knows bytecode which is a strict OO specification. It’s up to the makers of the language and its compiler to work within these constraints to create newer, more advanced language elements.

We first encountered this when we were working on adding Scala support to Takipi and had to dive deep into the Scala compiler. With Java 8 right around the corner, I thought it would be interesting to see how the Scala and Java compilers implement Lambda expressions. The results were pretty surprising.

To get things going I took a simple Lambda expression that converts a list of Strings to a list of their lengths.

In Java -

?
1
2
List names = Arrays.asList("1", "2", "3");
Stream lengths = names.stream().map(name -> name.length());
In Scala -

?
1
2
val names = List("1", "2", "3")
val lengths = names.map(name => name.length)
Don’t be tricked its simplicity – behind the scenes some complex stuff is going on.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
 楼主| 发表于 2014-1-27 21:23 | 显示全部楼层
The Code

I used javap to view the bytecode contents of the .class produced by the Scala compiler. Let’s look at the result bytecode (this is what the JVM will actually execute).

?
1
2
3
4
5
6
// this loads the names var into the stack (the JVM thinks
// of it as variable #2).
// It’s going to stay there for a while till it gets used
// by the <em>.map</em> function.

aload_2
Next, things gets more interesting – a new instance of a synthetic class generated by the compiler is created and initialized. This is the object that from the JVM’s perspective holds the Lambda method. It’s funny that while the Lambda is defined as an integral part of our method, in reality it lives completely outside of our class.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new myLambdas/Lambda1$$anonfun$1 //instantiate the Lambda object
dup //put it into the stack again

// finally, invoke the c’tor. Remember - it’s just a plain object
// from the JVM’s perspective.
invokespecial myLambdas/Lambda1$$anonfun$1/()V

// these two (long) lines loads the immutable.List CanBuildFrom factory
// which will create the new list. This factory pattern is a part of
// Scala’s collections architecture
getstatic scala/collection/immutable/List$/MODULE$
Lscala/collection/immutable/List$;
invokevirtual scala/collection/immutable/List$/canBuildFrom()
Lscala/collection/generic/CanBuildFrom;

// Now we have on the stack the Lambda object and the factory.
// The next phase is to call the .<em>map</em>() function.
// If you remember, we loaded the <em>names</em> var onto
// the stack in the beginning. Now it’ll gets used as the
// this for the .<em>map</em>() call, which will also
// accept the Lambda object and the factory to produce the
// new list of lengths.

invokevirtual scala/collection/immutable/List/map(Lscala/Function1;
Lscala/collection/generic/CanBuildFromLjava/lang/Object;
But hold on – what’s going on inside that Lambda object?

The Lambda object

The Lambda class is derived from scala.runtime.AbstractFunction1. Through this the map() function can polymorphically invoke the overridden apply() whose code is below -

?
1
2
3
4
5
6
7
8
9
10
11
12
13
// this code loads this and the target object on which to act,
// checks that it’s a String, and then calls another apply overload
// to do the actual work and boxes its return value.
aload_0 //load this
aload_1 //load the string arg
checkcast java/lang/String //make sure it’s a String - we got an Object

// call another apply() method in the synthetic class
invokevirtual myLambdas/Lambda1$$anonfun$1/apply(Ljava/lang/StringI

//box the result
invokestatic scala/runtime/BoxesRunTime/boxToInteger(I)Ljava/lang/Integer
areturn
The actual code to perform the .length() operation is nested in that additional apply method which simply returns the length of the String as we expected.

Phew.. it was quite a long way to get here

?
1
2
3
aload_1
invokevirtual java/lang/String/length()I
ireturn
For a line as simple as we write above, quite a lot of bytecode is generated – an additional class and a bunch of new methods. This of course isn’t meant to dissuade us from using Lambdas (we’re writing in Scala, not C). It just goes to show the complexity behind these constructs. Just think of the amount of code and complexity that goes into compiling complex chains of Lambda expressions!

I was quite expecting Java 8 to implement this the same way, but was quite surprised to see that they took another approach completely.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
 楼主| 发表于 2014-1-27 21:23 | 显示全部楼层
The bytecode here is a bit shorter but does something rather surprising. It begins quite simply by loading the names var and invokes its .stream() method, but then it does something quite elegant. Instead of creating a new object that will wrap the Lambda function, it uses the new invokeDynamic instruction which was added in Java 7 to dynamically link this call site to the actual Lambda function.

?
1
2
3
4
5
6
7
8
9
10
11
aload_1 //load the names var

// call its stream() func
invokeinterface java/util/List.stream)Ljava/util/stream/Stream;

//invokeDynamic magic!
invokedynamic #0:apply)Ljava/util/function/Function;

//call the map() func
invokeinterface java/util/stream/Stream.map:
(Ljava/util/function/FunctionLjava/util/stream/Stream;
InvokeDynamic magic. This JVM instruction was added in Java 7 to make the JVM less strict, and allows dynamic languages to bind symbols at run-time, vs. doing all the linkage statically when the code is compiled by the JVM.

Dynamic Linking. If you look at the actual invokedynamic instruction you’ll see there’s no reference of the actual Lambda function (called lambda$0). The answer lies in the way invokedynamic is designed (which in itself deserves a full post), but the short answer is the name and signature of the Lambda, which in our case is -

?
1
2
// a function named lamda$0 that gets a String and returns an Integer
lambdas/Lambda1.lambda$0Ljava/lang/StringLjava/lang/Integer;
are stored in an entry in a separate table in the .class to which the #0 parameter passed to the instruction points. This new table actually changed the structure of the bytecode specification for the first time after a good few years, requiring us to adapt Takipi’s error analysis engine to it as well.

The Lambda code

This is the code for the actual Lambda expression. It’s very cookie-cutter – simply load the String parameter, call length() and box the result. Notice it was compiled as a static function to avoid having to pass an additional this object to it like we saw in Scala.

?
1
2
3
4
aload_0
invokevirtual java/lang/String.length)
invokestatic java/lang/Integer.valueOfI)Ljava/lang/Integer;
areturn
This is another advantage of the invokedynamic approach, as it allows us to invoke the method in a way which is polymorphic from the .map() function’s perspective, but without having to allocate a wrapper object or invoke a virtual override method. Pretty cool!

Summary. It’s fascinating to see how Java, the most “strict” of modern languages is now using dynamic linkage to power its new Lambda expressions. It’s also an efficient approach, as no additional class loading and compilation is needed – the Lambda method is simply another private method in our class.

Java 8 has really done a very elegant job in using new technology introduced in Java 7 to implement Lambda expressions in what is a very straightforward way. It’s pleasant in a way to see that even a “venerable” lady such as Java can teach us all some new tricks

使用道具 举报

回复
招聘 : 系统架构师
论坛徽章:
142
摩羯座
日期:2016-03-30 23:01:17秀才
日期:2015-07-31 14:17:16秀才
日期:2015-07-31 09:12:09秀才
日期:2015-07-28 10:22:54秀才
日期:2015-07-24 09:00:17秀才
日期:2015-07-20 08:54:46秀才
日期:2015-07-15 12:49:25秀才
日期:2015-07-09 09:23:47秀才
日期:2015-07-06 10:44:32秀才
日期:2015-07-06 10:34:54
发表于 2014-1-28 22:48 | 显示全部楼层
, 天虎,好好排版一下。

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
 楼主| 发表于 2014-1-31 21:57 | 显示全部楼层
又审核啊!

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表