У меня есть следующий простой код
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 возвращается примитивный длинный (ср. ARETURN
против LRETURN
).