JEP 215:javac 的分层归因分析
概述
在 javac
中实现一种新的方法类型检查策略,以加快参数位置中多态表达式的属性推导速度。
目标
实现一种用于类型检查多态表达式的新方法,分层属性(TA),它提供了:
-
通过实施一种减少对给定表达式进行属性标注所需(冗余)遍数的方法,提高了性能,并且
-
获得了与当前类型检查实现相同的结果。
非目标
虽然在修复现有类型检查机制中潜藏的错误时可能会导致一些变化,但改变可编译程序的空间并不是目标。编译器生成的消息也会有一些细微差异,这也是预料之中的。
显著改善当前代码的整洁性和可维护性也并非目标——尽管此 JEP 引入的一些更改可能会使代码更加整洁。
动机
当前用于实现 Java SE 8 多态表达式类型检查的方法被称为“推测性属性标注”(Speculative Attribution,SA);SA 的主要思想是针对不同的目标多次对同一语法树进行类型检查;这使得例如可以针对多个重载解析目标检查一个 lambda 表达式。
在重载解析过程中进行类型检查的能力是一种非常强大且灵活的技术,但其在性能方面的代价非常高。更准确地说,对于 N 个重载候选者,同一个参数表达式可能会被检查多达 N * 3 次(每个重载阶段各一次:严格、宽松、可变参数)+ 1 次(最终检查阶段)。如果参数表达式允许嵌套(例如,一个返回多方法调用的 lambda 表达式),这些因子需要相乘,从而导致属性调用的组合爆炸。
推测归因机制的调用次数呈指数增长,导致了性能问题,这些问题已被观察到并作为错误报告,例如 JDK-8077247、JDK-8078093 和 JDK-8055984。
描述
这个 JEP 提出了一种替代的、更有效的实现方案,用于支持在 javac
中对多态表达式进行类型检查。从概念上讲,在执行重载解析时,没有必要对表达式进行类型检查;实际上,参数表达式可以采用自底向上的方式进行类型检查——从而实现无属性标注的重载检查,或者该表达式与适用性无关(参见 JLS 15.12.2.2)——这意味着该表达式对重载解析的适用性检查没有贡献。例如,一个 lambda 表达式可以是显式的——在这种情况下,主体的类型检查可以在重载解析之前完成;或者它是隐式的,在这种情况下,重载解析期间不需要进行类型检查。
分层归属的主要思想是在重载解析之前,生成自下而上的结构类型(针对给定方法调用中出现的每个多态参数表达式生成一个结构类型),这些类型包含执行重载解析适用性检查所需的所有信息——即无需进一步进行归属。这样的结构类型可能包含部分推断的类型变量,这些变量将在稍后的阶段——调用类型推断期间(参见 JLS 18.5.2)——才被确定。对于以下参数表达式,将创建新的结构类型:
- Lambda 表达式,
- 条件多态表达式,
- 泛型方法调用,
- 带括号的多态表达式,
- 方法引用,以及
- 钻石运算符实例创建表达式。
由于上面提到的一些表达式允许嵌套,因此一个结构类型可以引用其他结构类型。例如,返回泛型方法调用的 lambda 表达式的情况可以用一个结构类型(针对 lambda 表达式)来建模,该结构类型指向另一个建模泛型方法调用的结构类型。在这种情况下,与结构类型相关的重载检查可以是递归的 —— 例如,lambda 表达式体中所有由返回表达式提到的结构类型都必须根据重载解析机制提供的目标类型进行检查。
结构类型通常会在为方法/构造函数调用的参数赋值时创建。在方法/构造函数调用之外的上下文中出现的多态表达式应按照当前的方式进行处理。
为了适当地实现嵌套泛型方法调用,还需要对类型推断进行一些更改;更具体地说,类型推断机制必须配备更好的保存和回滚功能,以便在处理涉及泛型方法调用的重载检查(这可能需要执行一些推断任务)时,不会永久污染与这些调用相关联的上下文信息。
测试
javac
已经包含了一套全面的回归测试,以证明 SA(服务抽象层)能够按预期工作;尽管这些测试大多属于黑盒测试,但它们应该能有效捕获分层归因实现中的错误。作为此 JEP(Java 改进提案)的结果,可能会增加一些额外的测试,特别是与已知性能瓶颈相关的测试。
依赖
这里提出的工作可能使两个相关的 JEP 受益:正确处理导入语句(JEP 216) 和 注解管道 2.0(JEP 217)。