5

Long、Double、または Boolean のプリミティブ値 (および簡単にするために削除したその他のもの) を格納できるラッパー クラスがあります。私の最初の素朴な実装では、ラップされた値を Any 型のフィールドに格納しただけで、値がボックス化されました。

ボクシングを削除してメモリ使用量を減らすために、ジェネリックを使用してみましたが、型消去のために何も保存されないことがわかりました。そこで @specialized を使ってみたのですが、驚くべき結果が得られました。

以下のコードは scalac 2.9.3 でビルドされ、JDK7 で実行されます。MemoryMeasurer はここから来ており、正確であると信じています。「パディング」フィールドは重要ではありません。基本オブジェクト (ラップされた値なし) を 16 バイトにパディングするために使用しているだけなので、さまざまな試行の効果がより明確になります。

import objectexplorer.MemoryMeasurer

class GenericNonSpecialized[A] (wrapped: A, val padding: Int) {
  def getWrapped: Any = wrapped
}

class GenericSpecialized[@specialized(Long, Double, Boolean) A] (wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class GenericSpecializedVal[@specialized(Long, Double, Boolean) A] (val wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class NonGeneric(val wrapped: Long, padding: Int) {
}

object App {
  def main(args: Array[String]) {
    println(MemoryMeasurer.measureBytes(new GenericNonSpecialized(4L, 0)))
    // Expect: 48: NonSpecialized object (24 bytes) + boxed long (24 bytes)
    // Actual: 48

    // I expect all of the below to be 24 bytes: Object overhead (12 bytes) + Long (8 bytes) + Int (4 bytes),
    // but only the non-generic one is actually 24 bytes.

    println(MemoryMeasurer.measureBytes(new GenericSpecialized(4L, 0))) // 56

    println(MemoryMeasurer.measureBytes(new GenericSpecializedVal(4L, 0))) // 32

    println(MemoryMeasurer.measureBytes(new NonGeneric(4L, 0))) // 24
  }
}

質問:

  1. 非ジェネリック版のように、24 バイトを使用するジェネリック ラッパー オブジェクトを作成するにはどうすればよいですか? (私の最善の試み、GenericSpecializedVal は 32 を使用)
  2. GenericNonSpecialized が 56 バイトを使用するのはなぜですか?「val」を追加して「ラップ」を実際のフィールドにすると、32 バイトに減少します。
4

1 に答える 1