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 minulepřed­mi­nule, Java je na tom velice špatně, když když musí kom­bi­no­vat pri­mi­tivní typy a ge­ne­rické pa­ra­me­try. V ta­ko­vých pří­pa­dech musí ze ska­lární hod­noty vy­ro­bit objekt au­to­bo­xin­gem.

Jedna ze za­jí­ma­vých vlast­ností jazyka Scala, která by v takové si­tu­aci mohla pomoci je ano­tace @specialized. Ta zařídí, že kom­pi­lá­tor vy­ge­ne­ruje ně­ko­lik spe­ci­a­li­zo­va­ných tříd (jednu pro každou kom­bi­naci pri­mi­tiv­ního typu a spe­ci­a­li­zo­va­ného ty­po­vého pa­ra­me­tru) a vždy se snaží použít tu nej­spe­ci­fič­tější in­stanci, aby kód ne­mu­sel ztrá­cet čas au­to­bo­xin­gem a na­há­ně­ním ob­jektů na haldě.

To pak zna­mená, že pra­cu­jeme se spe­ci­a­li­zo­va­nými tří­dami, které mají přímo atri­buty pri­mi­tiv­ních typů a nejsou nuceny uklá­dat bo­xo­vané ob­jekty.

V praxi to vypadá ná­sle­dovně. Máme třídu X jejíž pa­ra­metr T chceme spe­ci­a­li­zo­vat pro IntDouble:

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

Kom­pi­lá­tor vy­ge­ne­ruje 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

Kom­pi­lá­tor ze sta­ticky zná­mých typů pozná, že chceme třídu spe­ci­a­li­zo­va­nou pro Int a kód po­u­žívá metody pro pri­mi­tivní Int.

Všim­něte si ale, že spe­ci­a­li­zo­vané va­ri­anty dědí od ne­spe­ci­a­li­zo­va­ného předka. Ob­sa­hují tedy jeho atri­but x, který ale sami nikdy ne­po­u­žijí. A to zna­mená, že spe­ci­a­li­zo­vané ob­jekty jsou o něco málo větší, než ty ne­spe­ci­a­li­zo­vané.

Spe­ci­a­li­zace byla do Scaly při­dána jenom kvůli rych­losti, aby kód ne­mu­sel pra­co­vat s bo­xo­va­nými pri­mi­tivy, které se na­chá­zejí na haldě a pří­stup k nim vždycky zna­mená skok na poin­ter a pa­mě­ťová lo­ka­lita jde do háje. Hlavní sta­rostí nebyla re­dukce ve­li­kosti ob­jektů.

Ale spe­ci­a­li­zace přece ušetří paměť, pro­tože jedna re­fe­rence je mnohem menší než bo­xo­vaný pri­mi­tivní typ.

Pro pří­klad si vez­měme Tuple2, který je spe­ci­a­li­zo­vaný pro oba prvky. Na 64bit plat­for­mách s kom­pri­mo­va­nými poin­tery je si­tu­ace ná­sle­du­jící:

ne­spe­ci­a­li­zo­vaný (Any, Any) hla­vička + 2 re­fe­rence 24B (nebo 32B bez kom­pri­mace oops)
ne­spe­ci­a­li­zo­vaný (In­te­ger, In­te­ger) hla­vička + 2 re­fe­rence + 2 boxed int 56B (nebo 80B)
spe­ci­a­li­zo­vaný (Int, Int) hla­vička + 2 ne­po­u­ží­vané re­fe­rence + 2 int 32B (nebo 40B)

V tomto pří­padě spe­ci­a­li­zace ušetří +- po­lo­vinu místa a má jenom os­mi­baj­to­vou režii proti ide­ál­nímu stavu.

Ano­tace @spe­ci­a­li­zed se dá využít i pro spe­ci­a­li­zaci polí, kdy se kom­pi­lá­tor po­stará, že se sku­tečně po­u­ží­vají pole pri­mi­tiv­ních typů. Jenom to může ušet­řit 75% paměti. Ale si­tu­ace se spe­ci­a­li­zo­va­nými poli je po­ně­kud kom­pli­ko­va­nější, takže se o tom ro­ze­píšu někdy příště.

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