IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Lambda表达式为什么运行效率低 -> 正文阅读

[大数据]Lambda表达式为什么运行效率低

目录

准备:

分析

不使用Lambda表达式执行流程

使用Lambda表达式执行循环流程


准备:

我为什么说Lambda表达式运行效率低.

先准备一个list:

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
	list.add(i);
}

先用Lambda表达式的方式来循环一下这个list:

long lambdaStart = System.currentTimeMillis();
list.forEach(i -> {
	// 不用做事情,循环就够了
});
long lambdaEnd = System.currentTimeMillis();
System.out.println("lambda循环运行毫秒数===" + (lambdaEnd - lambdaStart));

运行时间大概为110ms

再用普通方式来循环一下这个list:

long normalStart = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
	// 不用做事情,循环就够了
}
long normalEnd = System.currentTimeMillis();
System.out.println("普通循环运行毫秒数===" + (normalEnd - normalStart));

运行时间大概为0ms或1ms

大家看到了吧,运行时间差距就是这么大,不信大家可以试试,并且不是只有在循环的时候Lambda表达式才会导致运行效率低,而是Lambda表达式在运行的时候需要额外的时间,我们继续分析。

分析

如果我们有研究Lambda表达式,最正确,最直接的方法就是查看他所对应的字节码指令。

使用以下命令查看class文件对应的字节码指令:

javap -v -p Test.class

上述命令解析出来的指令很多,我这里提取比较重要的部分来给大家分析:

使用Lambda表达式所对应的字节码指令如下:

34: invokestatic  #6        // Method java/lang/System.currentTimeMillis:()J
37: lstore_2
38: aload_1
39: invokedynamic #7,  0    // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
44: invokeinterface #8,  2  // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
49: invokestatic  #6        // Method java/lang/System.currentTimeMillis:()J

不使用Lambda表达式所对应的字节码指令如下:

82: invokestatic  #6          // Method java/lang/System.currentTimeMillis:()J
85: lstore        6
87: iconst_0
88: istore        8
90: iload         8
92: aload_1
93: invokeinterface #17,  1   // InterfaceMethod java/util/List.size:()I
98: if_icmpge     107
101: iinc          8, 1
104: goto          90
107: invokestatic  #6         // Method java/lang/System.currentTimeMillis:()J

从上面两种方式所对应的字节码指令·可以看出,两种方式的执行方式确实不太一样。

不使用Lambda表达式执行流程

字节码指令执行步骤:

82: invokestatic? #6????????? 执行静态方法,// Method java/lang/System.currentTimeMillis:()J
85: - 92:简单来说就是初始化数据,int i=0;
93: invokeinterface #17,执行接口方法,接口List,所以执行的就是ArrayList.size方法
98: if_icmpge???? 比较,相当于执行i<list.size();
101: iinc????????? i++
104: goto????????? 进行下一次循环
107: invokestatic? #6?????? 执行静态方法

那么这个流程大家应该问题不大,是一个正常的循环逻辑.

使用Lambda表达式执行循环流程

字节码指令执行步骤:

34: invokestatic? #6???? 执行静态方法 ? //? java/lang/System.currentTimeMillis:()J
37: -38: 初始化数据
39: invokedynamic #7,这是在干什么?
44: invokeinterface #8, 执行/List.forEach:(Ljava/util/function/Consumer;)V
49: invokestatic? #6????? 执行静态方法? // Method java/lang/System.currentTimeMillis:()

?和上面正常循环的方式的字节码指令不太一样,我们认真的看一下这个字节码指令,这个流程并不像是一个循环的流程,而是一个方法顺序执行的流程:

先初始化一些数据
执行invokedynamic指令
然后执行List.forEach()方法,所以真证的循环逻辑在这里

所以呀,Lambda表达式循环时,再循环前会做一些事情,所以导致执行时间要更长一些:

那么invokedynamic指令到底做什么事情?

List.forEach方法接受一个参数,Consumer<? super T> action,Consumer是个接口,所以如果要调用此方法,就要传递该接口类型的对象。

代码里实际传递的是Lambda表达式,那么我们这里可以假设,需要将Lambda表达式转换为对象,对象的类型需要根据Lambda表达式所使用的地方在编译时期进行反推。

反推的解释:Lambda表达式可以被多个方法使用,此方法接受的参数类型,也就是函数式接口,是可以不一样的,只要函数式接口符合表达式的定义即可。

那么如果将Lambda表达式转换为一个对象,就需要实现Consumer接口。

问题就是这类什么时候生成的,并且声称在哪里?

我们应该想到,invokedynamic指令,他是不是先将Lambda表达式转换为某个类,然后生成一个实例以便forEach调用?

指令如下:

invokedynamic #7,  0    // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;

java中调用函数有四大指令:invokevirtual,invokespecial,invokestatic,invokeinterface,在JSR 292添加一个新的指令invokedynamic,这个指令表示执行动态语言,也就是Lambda表达式.

该指令注释中的#0表示的是BootstrapMethods中第0个方法:

BootstrapMethods:
  0: #60 invokestatic 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;
    Method arguments:
      #61 (Ljava/lang/Object;)V
      #62 invokestatic com/luban/Test.lambda$main$0:(Ljava/lang/Integer;)V
      #63 (Ljava/lang/Integer;)V

所以invokedynamic方法执行时,实际上就是执行BootstrapMethods方法,java/lang/invoke/LambdaMetafactory.metafactory

代码如下:

public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

此方法用到一个特别明显且易懂得类:InnerClassLambdaMetafactory.

这类是一个针对Lambda表达式生成内部类的工厂类,调用buildCallSite()方法会生成一个内部类以及所属实例。

那么现在生成一个内部类,需要一些什么条件呢:

类名:可按一些规则生成

类需要实现的接口:编译时就已知了,本例中就是Consumer接口

实现接口里面的方法:本例中Consumer接口的void accept(T t )方法.

如何实现void accept(T t )方法呢?

在javap -v -p Test.class 的结果另外的方法:

private static void lambda$main$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 25: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0     i   Ljava/lang/Integer;

很明显,这个静态的lambda$main$0方法代表的就是我们写的lambda表达式,只是因为我们离子中的lambda表达式没写逻辑,所以这段字节码指令部分没什么内容。

那么我们在实现内部类的void accept(T t )方法时,只要调用一个这个lambda$main$0静态方法即可。

所以到此,一个内部类就可以被正常的实现出来了,内部类有了之后,Lambda表达式就是可以被转换成这个类的内部类对象,就可以进行循环了。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-01-04 13:29:40  更:2022-01-04 13:29:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:24:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码