java - 使用专门类型进行 Scala 优化 : sum with longs

标签 java scala performance java-stream bytecode

我有以下简单的代码

val longs: Vector[Long] = (1L to 1000000L).toVector

以及所谓的等效 Java

def jLongs: java.util.stream.LongStream = java.util.stream.LongStream
    .iterate(1L, (i: Long) => i <= 1000000L, (i: Long) => i + 1L)

当我使用以下代码运行基准测试时

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
class BoxingScala {
  val longs: Vector[Long] = (1L to 1000000L).toVector
  def jLongs: java.util.stream.LongStream = java.util.stream.LongStream
    .iterate(1L, (i: Long) => i <= 1000000L, (i: Long) => i + 1L)

  @Benchmark def a: Long = longs.sum

  @Benchmark def b: java.lang.Long = jLongs.sum()
}

我发现 Java 代码大约快了 400%。当我试图理解原因时,我发现了这个字节码:

  // access flags 0x1
  public a()J
  @Lorg/openjdk/jmh/annotations/Benchmark;()
   L0
    LINENUMBER <benchmark a> L0
    ALOAD 0
    INVOKEVIRTUAL gurghet/BoxingScala.longs ()Lscala/collection/immutable/Vector;
    GETSTATIC scala/math/Numeric$LongIsIntegral$.MODULE$ : Lscala/math/Numeric$LongIsIntegral$;
    INVOKEVIRTUAL scala/collection/immutable/Vector.sum (Lscala/math/Numeric;)Ljava/lang/Object;
    INVOKESTATIC scala/runtime/BoxesRunTime.unboxToLong (Ljava/lang/Object;)J
    LRETURN
   L1
    LOCALVARIABLE this Lgurghet/BoxingScala; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public b()Ljava/lang/Long;
  @Lorg/openjdk/jmh/annotations/Benchmark;()
   L0
    LINENUMBER <benchmark b> L0
    GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
    ALOAD 0
    INVOKEVIRTUAL gurghet/BoxingScala.jLongs ()Ljava/util/stream/LongStream;
    INVOKEINTERFACE java/util/stream/LongStream.sum ()J (itf)
    INVOKEVIRTUAL scala/Predef$.long2Long (J)Ljava/lang/Long;
    ARETURN
   L1
    LOCALVARIABLE this Lgurghet/BoxingScala; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 1

其中 longs 初始化为

 L1
    LINENUMBER <init> L1
    ALOAD 0
    NEW scala/runtime/RichLong
    DUP
    GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
    LCONST_1
    INVOKEVIRTUAL scala/Predef$.longWrapper (J)J
    INVOKESPECIAL scala/runtime/RichLong.<init> (J)V
    LDC 1000000
    INVOKESTATIC scala/runtime/BoxesRunTime.boxToLong (J)Ljava/lang/Long;
    INVOKEVIRTUAL scala/runtime/RichLong.to (Ljava/lang/Object;)Lscala/collection/immutable/NumericRange$Inclusive;
    INVOKEVIRTUAL scala/collection/immutable/NumericRange$Inclusive.toVector ()Lscala/collection/immutable/Vector;
    PUTFIELD gurghet/BoxingScala.longs : Lscala/collection/immutable/Vector;

所以在我看来,scala 版本被迫加载一百万个对象。

这就是速度这么慢的原因吗?我怎么知道要专门做多头?

此外,有趣且违反直觉的事实是,虽然 java 代码返回一个对象,但在 scala 中返回一个原始 long (参见 ARETURNLRETURN >).

最佳答案

查看字节码是徒劳的。区别在于Stream是什么LongStream 按需生成元素。它不是一种数据结构;而是一种数据结构。它是一个控制结构——一些其他数据源上的潜在循环。您的 Java 归结为

var sum: Long = 0
for(i <- 1L to 1000000L) sum += i;

Vector 是一个实际的数据结构,它实际上必须存储 100000 个 long,这使得你的 Scala 版本本质上

val oops = new Array[java.lang.Long](1000000) // boxed!
for(i <- 0 until 1000000) oops(i) = i + 1
var sum: Long = 0
for(i <- 0 until 1000000) sum += oops(i)

它们之间绝对不存在等同关系。另请注意,1L 到 1000000L 是一个 NumericRange[Long],它已经是一个 Scala 集合,而 (1 到 1000000L) .sum 比其中任何一个都要快得多,因为它使用简单的算术公式来计算结果。最接近 LongStream 的实际上是 SeqView[Long],您可以将其作为 (1L to 1000000).view 获取。如果您对此调用 sum ,我相信集合库不够智能,无法将其简化为对 NumericRangesum 调用,并且相反,会像 Java 版本中一样迭代它,进行更仔细的比较。不过,它不会专门用于Long,因此它仍然会受到拳击惩罚。

关于java - 使用专门类型进行 Scala 优化 : sum with longs,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61050081/

相关文章:

java - 如何将 JDialog 链接到 JButton 单击

scala - sbt 脚本插件失败,因为未解决对发布交叉编译的 scala 版本的依赖

java - 如何将迭代器中的值传递给paintComponent

java - 发生 OutOfMemoryError : Java heap space in play framework

scala - 如何仅向特定类型的列表添加额外的行为?

c - 在包装好的 SSE 花车上翻转标志

c# - C#LazyCache并发字典垃圾回收

html - 在 HTML5 Canvas 中绘制 X 数量的圆圈的最快方法是什么?

java - 如何创建一个由用户输入定义长度的静态数组?

scala - If 语句范围的变量