Scala - почему Double потребляет меньше памяти, чем Floats в этом случае?

Здесь странное поведение, в которое я попал, и я не могу найти никакого намека на то, почему это так. Я использую в этом примере метод оценки SizeEstimator от Spark, но я не обнаружил никаких сбоев в их коде, поэтому мне интересно, почему - если они предоставляют хорошая оценка памяти - почему у меня это есть:

val buf1 = new ArrayBuffer[(Int,Double)]
var i = 0
while (i < 3) {
   buf1 += ((i,i.toDouble))
   i += 1
}
System.out.println(s"Raw size with doubles: ${SizeEstimator.estimate(buf1)}")
val ite1 = buf1.toIterator
var size1: Long = 0l
while (ite1.hasNext) {
   val cur = ite1.next()
   size1 += SizeEstimator.estimate(cur)
}
System.out.println(s"Size with doubles: $size1")

val buf2 = new ArrayBuffer[(Int,Float)]
i = 0
while (i < 3) {
   buf2 += ((i,i.toFloat))
   i += 1
}
System.out.println(s"Raw size with floats: ${SizeEstimator.estimate(buf2)}")
val ite2 = buf2.toIterator
var size2: Long = 0l
while (ite2.hasNext) {
   val cur = ite2.next()
   size2 += SizeEstimator.estimate(cur)
 }
 System.out.println(s"Size with floats: $size2")

Выход на консоль печатает:

Raw size with doubles: 200
Size with doubles: 96
Raw size with floats: 272
Size with floats: 168

Итак, мой вопрос довольно наивный: почему в этом случае float имеет больше памяти, чем удваивается? И почему это становится еще хуже, когда я превращаю его в итератор (в первом случае соотношение 75% составляет 50% при преобразовании в итератор!).

(Чтобы иметь больше контекста, я впал в это, пытаясь "оптимизировать" приложение Spark, изменив Double на Float и выяснив, что на самом деле это заняло больше памяти с поплавками, чем с удвоением...)

P.S.: это не из-за малого размера буферов (здесь 3), если я ставлю 100, я получаю:

Raw size with doubles: 3752
Size with doubles: 3200
Raw size with floats: 6152
Size with floats: 5600

и поплавки все еще потребляют больше памяти... Но соотношение стабилизировалось, поэтому кажется, что разные отношения при преобразовании в итератор должны быть обусловлены некоторыми накладными расходами, которые, как я полагаю.

EDIT: Похоже, что Product2 фактически специализируется только на Int, Long и Double:

trait Product2[@specialized(Int, Long, Double) +T1, @specialized(Int, Long, Double) +T2] extends Any with Product

Кто-нибудь знает, почему Float не учитывается? Ни Short, что приводит к странным поведением...

Ответ 1

Это связано с тем, что Tuple2 @specialized для Double, но не специализировано для Float.

Это означает, что (Int,Double) будет представлен как структура с двумя полями примитивных типов java int и Double, тогда как (Int,Float) будет представлен как структура с int и типом оболочки java.lang.Float

Подробнее обсуждение здесь