Skip to content

lamda、stream能做哪些优化 #57

Description

@lfeng14

Lambda和Stream本身是高级抽象,运行时通常会变成对象分配、接口调用、MethodHandle调用和多层Sink调用。编译器要做的是把这些抽象逐层“拆掉”。

例如:

long sum = points.stream()
    .mapToLong(p -> p.x * p.x)
    .filter(v -> v > 10)
    .sum();

未经充分优化时,大致是:

Stream对象
 → Map Sink对象
 → Filter Sink对象
 → Reduce Sink对象
 → Function接口调用
 → lambda对象
 → 装箱/拆箱

优化充分后,效果接近:

long sum = 0;
for (Point p : points) {
    long v = p.x * p.x;
    if (v > 10) {
        sum += v;
    }
}

主要需要以下能力。

优先级 | 优化能力 | 作用 -- | -- | -- P0 | _allocateInstance intrinsic | 避免lambda、Sink、Collector等对象通过VM通用慢路径分配 P0/P1 | MethodHandle/invokedynamic目标解析 | 把lambda调用解析到真实的lambda$xxx方法 P1 | Type PGO、去虚拟化 | 将Function.apply、Sink.accept等接口调用变成直接调用 P1 | late/incremental inline | 热点信息稳定后,把整条Stream/lambda调用链继续内联 P1 | EA/PEA、标量替换 | 消除不逃逸的lambda、Stream stage、Sink、迭代器和Ref对象 P1 | Stream/Sink链融合 | 合并多层accept/opWrapSink/copyInto调用,暴露真正的数据循环 P2 | 装箱消除 | 消除Integer.valueOf、Long.valueOf及对应拆箱 P2 | 循环优化 | 内联和EA完成后,再做边界检查消除、LICM、展开和向量化 P2 | GC barrier优化 | 对已消除或新创建的临时对象删除、合并不必要的barrier

Lambda优化的关键链路

一段lambda:

items.forEach(x -> consume(x.value));

运行时可能是:

invokedynamic
 → Lambda对象
 → Consumer.accept接口调用
 → lambda$xxx
 → consume

编译器需要依次完成:

解析invokedynamic/MethodHandle目标
 → 根据类型profile去虚拟化Consumer.accept
 → 内联lambda$xxx
 → EA判断lambda对象不逃逸
 → 消除lambda对象分配

缺少其中任意一环,后续优化都会受影响。例如目标没有解析出来,EA就看不到lambda内部;方法没有内联,循环优化也看不到实际计算。

Stream优化的关键链路

Stream更复杂,因为每个stage通常对应一个对象:

ReferencePipeline
 → map Sink
 → filter Sink
 → reduce Sink
 → Spliterator

理想优化顺序是:

类型特化
 → 内联Sink.accept
 → 内联lambda
 → 消除Stream/Sink/Iterator对象
 → 合并调用链
 → 暴露循环
 → 循环和向量优化

对当前用例的对应关系

  1. scrabble

    当前第一问题不是泛泛的Stream优化,而是 _allocateInstance。C2关闭该intrinsic后从约51 ms变成79 ms,已经接近C3的80 ms。补齐后再看:

    • IntPipeline$1.opWrapSink
    • AbstractPipeline.wrapSink
    • copyIntoWithCancel
    • Collectors.summingLong
    • late inline和Stream/Sink对象消除
  2. rx-scrabble

    RxJava虽然不是JDK Stream,但结构类似,是Observer/operator/lambda链。优先考虑:

    • _allocateInstance
    • VarHandle/MethodHandle目标解析
    • Observer回调去虚拟化
    • Rx operator、Disposable和Observer对象的EA
  3. scala-kmeans

    findClosest → Vector.foreach → lambda → squareDistance在C3中已经基本内联,因此这里不是“lambda没内联”。优先级是:

    • _allocateInstance
    • DoubleRef/ObjectRef及闭包对象的EA/标量替换
    • 最后才是Vector循环优化

简单说,Lambda/Stream优化不是增加一个单独的“Lambda优化pass”,而是一条能力链:

MethodHandle解析 → Type PGO去虚拟化 → late inline → EA/标量替换 → Stream链融合 → 循环优化。

当前最应该先做的是_allocateInstance;否则大量时间还停留在对象分配runtime路径,后面的Stream和lambda优化收益很难正确评估。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions