Velikost objektů na JVM – Scala @specialized

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ě.

Flattr this!

This entry was posted in JVM, Paměť, Scala. Bookmark the permalink.

3 Responses to Velikost objektů na JVM – Scala @specialized

  1. v6ak says:

    Anotace @specialized nemusí ušetřit nic, pokud nemáme pole a používáme primitivní typy malého rozsahu (v případě Integeru, tuším, –127 až 127).

    Jinak by mě zajímalo, jestli je nějaký problém s tím, že by Scala zmíněný kód zkompilovala zhruba takto:

    abstract class X {
    // hlavně abstraktní metody, které jsou implementovány u potomků
    }

    class X$mcO$sp extends 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
    }

    (Chybí mi tu tag pre, snad se to zobrazí dobře.)

    Samozřejmě u obou tříd specializovaných na primitivní typ (tj. X$mcI$sp a X$mcD$sp) musí implementovat i metody x()Ljava/lang/Ob­ject; a x_$eq(Ljava/lan­g/Object;)V. Ale to tak jako tak.

    • Napadají mě jenom dvě věci: interoperabilita s Javou, protože v ní bych musel explicitně psát že chci X$mcO$sp místo X a pak nejspíš snadnější dědění, které nepotřebuje podporu kompilátoru. V současném schématu je možné, že specializovaná třída má normálního nespecializovaného potomka.

      • v6ak says:

        Dědění (aspoň ze Scaly) nebude IMHO velký problém:

        • V případě nespecializovaného potomka a specializovaného předka se moc nezmění, jen by se muselo dědit z java.lang.Object-ové varianty. Nejspíš žádná velká změna.
        • Specializované dědění by bylo dokonce čistější. Dnes to při dědění z třídy patrně moc nefunguje (možná je změna od verze 2.10, nevím), dá se narazit na takovouto hlášku: warning: class X must be a trait. Specialized version of class Z will inherit generic X[Double]

        Interoperabilita s Javou a případně dalšími jazyky už je zajímavější problém. To by mohl být skutečný důvod, i když asi i to bude nějakými způsoby řešitelné. Například ta obecná implementace přes java.lang.Object by mohla mít speciální jméno, např. X$Generic, což by se v Javě už asi dalo používat dobře. (Dva a více parametrů nevadí, X$Generic by se použilo jen pro případ, že všechny typové parametry jsou kompatibilní s java/lang/Ob­ject.) Nebo továrny.

Leave a Reply

Your email address will not be published. Required fields are marked *