funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

Velikost objektů na JVM - Scala @specialized

30. 1. 2013 — k47

Jak jsem ukázal minule a předminule, Java je na tom velice špatně, když když musí kombinovat primitivní typy a generické parametry. V takových případech musí ze skalární hodnoty vyrobit objekt autoboxingem.

Jedna ze zajímavých vlastností jazyka Scala, která by v takové situaci mohla pomoci je anotace @specialized. Ta zařídí, že kompilátor vygeneruje několik specializovaných tříd (jednu pro každou kombinaci primitivního typu a specializovaného typového parametru) a vždy se snaží použít tu nejspecifičtější instanci, aby kód nemusel ztrácet čas autoboxingem a naháněním objektů na haldě.

To pak znamená, že pracujeme se specializovanými třídami, které mají přímo atributy primitivních typů a nejsou nuceny ukládat boxované objekty.

V praxi to vypadá následovně. Máme třídu X jejíž parametr T chceme specializovat pro Int a Double:

class X[@specialized(Int, Double) T] {
  var x: T = _
}

Kompilátor vygeneruje tyto třídy:

class X {
  var x: Object
}

class X$mcI$sp extends X {
  var x$mcI$sp: Int
}

class X$mcD$sp extends X {
  var x$mcD$sp: Double
}

když pak voláme v kódu

val x = new X[Int]
x.x = 1

Kompilátor ze staticky známých typů pozná, že chceme třídu specializovanou pro Int a kód používá metody pro primitivní Int.

Všimněte si ale, že specializované varianty dědí od nespecializovaného předka. Obsahují tedy jeho atribut x, který ale sami nikdy nepoužijí. A to znamená, že specializované objekty jsou o něco málo větší, než ty nespecializované.

Specializace byla do Scaly přidána jenom kvůli rychlosti, aby kód nemusel pracovat s boxovanými primitivy, které se nacházejí na haldě a přístup k nim vždycky znamená skok na pointer a paměťová lokalita jde do háje. Hlavní starostí nebyla redukce velikosti objektů.

Ale specializace přece ušetří paměť, protože jedna reference je mnohem menší než boxovaný primitivní typ.

Pro příklad si vezměme Tuple2, který je specializovaný pro oba prvky. Na 64bit platformách s komprimovanými pointery je situace následující:

nespecializovaný (Any, Any) hlavička + 2 reference 24B (nebo 32B bez komprimace oops)
nespecializovaný (Integer, Integer) hlavička + 2 reference + 2 boxed int 56B (nebo 80B)
specializovaný (Int, Int) hlavička + 2 nepoužívané reference + 2 int 32B (nebo 40B)

V tomto případě specializace ušetří +- polovinu místa a má jenom osmibajtovou režii proti ideálnímu stavu.

Anotace @specialized se dá využít i pro specializaci polí, kdy se kompilátor postará, že se skutečně používají pole primitivních typů. Jenom to může ušetřit 75% paměti. Ale situace se specializovanými poli je poněkud komplikovanější, takže se o tom rozepíšu někdy příště.

@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články