Effizienz primitiver Datentypen im ControlState

February 25, 2008 22:10 by Andre Loker

ASP.NET 2 bietet neben dem ViewState auch den so genannten ControlState an, um Control-Zustände über Postbacks hinweg zu speichern. In der Standardeinstellung wird der ControlState wie der ViewState in das versteckte Feld "__VIEWSTATE" der Seite serialisiert. Informationen dazu, wie man ControlStates verwendet, gibt es genügend im Internet.

Durch Zufall bin ich heute aber auf eine interessante Tatsache gestoßen. Natürlich sollten die Daten des ControlStates so klein wie möglich gehalten werden. In meinem konkreten Fall musste das Control lediglich die ID aus einer Datenbank speichern. Der Wert war vom Typ Int64, was 8 bytes zur Laufzeit entspricht. Ich erwartete also etwas mehr als diese 8 bytes im ControlState. Zur Kontrolle habe ich mir trace.axd zur Hilfe genommen, um die Größe des ControlStates zu überprüfen - und war doch ziemlich überrascht, dass dieser eine Int64 sage und schreibe 132 bytes veranschlagte. Das schien mir doch reichlich viel und veranlasste mich dazu, genauer zu hinterfragen, warum so der Wert so groß ist.

Versuchsweise verwendete ich statt des Int64 einen "gewöhnlichen" Int32 und erhielt als ControlState Größe nunmehr lediglich 8 bytes. Mir wurde dann relativ schnell klar, dass die Wahl des Datentyps von entscheidender Bedeutung ist. Ich habe also eine Testreihe gestartet, in der ich verschiedenste primitive Datentypen als ControlState verwendete. Dabei wurde auch deutlich, dass nicht nur der Datentyp eine Rolle spielte, sondern auch der Wert(!). Kleine Integer wurden offenbar kompakter serialisiert als große.

Ergebnisse

Ganzzahlige und Fließkommadatentypen

Die erste "Disziplin" ist das Speichern einfacher numerischer Datentypen, sowohl ganzzahlige Typen als auch Fließkommatypen. Getestet habe ich einmal den Wert "0" für jeden Datentyp und den höchsten Wert, welcher durch das konstante Feld MaxValue bei allen Typen angegeben ist.

Datentyp ControlState:
Wert 0
ControlState:
MaxValue
byte 8 8
sbyte 132 136
short 12 12
ushort 132 140
int 8 16
uint 132 144
long 132 156
ulong 132 160
float 12 12
double 20 20
decimal 136 172

 

Ergebnis: Byte, short, int float und double werden gesondert behandelt und sehr effizient gespeichert. Alle vorzeichenlosen Ganzzahlen (Ausnahme: bytes - dort sind es die vorzeichenbehafteten) sowie long, ulong und decimal sind "teuer". Für sie besteht offensichtlich keine effiziente Serialisierung im Framework.

 

Strings und Booleans

Strings und Boolsche Werte.

Wert ControlState
true 8
false 8
"test" 16
string.Empty 8

 

Ergebnis: sowohl Boolsche Werte als auch Strings werden sehr effizient serialisiert.

 

arrays

Falls man mehrere Werte als ControlState speichern möchte, wäre es ggf. interessant, diese als Array zu serialisieren. Ich habe einfache object-Arrays getestet als auch einige spezialisierte arrays (byte und int).

Array inhalt ControlState:
Wert 0 bzw String.Empty
ControlState:
MaxValue bzw. "test
"
object[] { int } 12 20
object[] { int, int } 16 28
object[] { int, int, int } 16 36
object[] { int, int, int, int } 16 44
object[] { byte } 16 16
byte[] { byte } 132 132
int[] { int } 12 20
string[] {string } 12 16

 

Ergebnis: object-Arrays scheinen sehr kompakt gespeichert zu werden. Ebenso sind int und string Arrays sehr effizient. Andere Arraytypen scheinen nicht gesondert behandelt zu werden.

 

8 bytes als speichern

In meinem konkreten Beispiel möchte ich einen Int64 als ControlState speichern. Die folgende Tabelle zeigt, wie ich dieses 8 bytes ggf. auch als ein Array von kleineren Zahlenwerten speichern könnte, indem ich die 8 bytes aufteile.

Array ControlState:
Wert 0
ControlState:
MaxValue
object[] { 8x byte } 32 32
object[] { 4x short } 28 28
object[] { 2x int } 16 28

 

Ergebnis: wie schon zuvor siegt der Int32-Typ. Doch selbst, wenn der Int64 Wert als ein Array von 8 bytes gespeichert wird, beträgt die Größe höchstens ein Viertel im Vergleich zum serialisierten Int64

 

Pair und Triplet

Als Alternative zum Array bieten sich für ControlStates, die aus zwei oder drei Werten bestehen, die Typen Pair und Triplet im System.Web.UI Namespace an. Die folgende Tabelle zeigt die ControlState Größe bei Verwendung des Pairs oder Triplets und zum Vergleich dazu noch einmal gleichwertige Arrays mit zwei bzw. drei Werten.

Inhalt ControlState:
Wert 0
ControlState:
MaxValue
Pair(int, int) 12 24
object[] { int, int } 16 28
Triplet(int, int, int) 12 32
object[] { int, int, int } 16 36

 

Ergebnis: offensichtlich sind Pair und Triplet nochmal jeweils 4 bytes kleiner als ihr Array-Pendant.

Fazit

Es ist offensichtlich, dass einige Datentypen gesondert serilisiert werden. Mit einer klugen Auswahl des Datentypes, kann die Größe des ControlStates dramatisch verkleinert werde, je nach Fall zwischen 75% und 90%.

Einige Richtlinien, die man aus den Tabellen entnehmen kann:

  • int (Int32) ist generell der bevorzugte Datentyp für Ganzzahlen.
  • Im ControlState zu empfehlen sind nur folgende primitive Typen: byte, short, int, float, double, bool und string.
  • Um zwei oder drei Werte zu speichern, sollten Pair bzw. Triplet verwendet werden
  • Bei mehr als drei Werten können Arrays verwendet werden. In der Regel sollte das ein object[] Array sein. Bei int und string sind int[] respektive string[] gleich effizient gegenüber object[].
  • 64 bit Integer sollten zum Speichern mittels Bitmaske in 2 32 bit Integer geteilt und als Pair gespeichert werden
  • sbyte, ushort und ulong sollten zunächst auf Bitniveau in die effizienten Pendants byte, short und long gebracht werden.