String intern pool

L’Intern pool, di cui non è la prima volta che ne parlo, è un hashtable mantenuto internamente dal CLR contenente stringhe generalmente usate per memorizzare valori costanti. In questo hashtable sono memorizzate le costanti stringa a livello di compilazione ed ogni stringa aggiunta esplicitamente attraverso l’utilizzo del metodo string.Intern, il quale aggiunge una stringa nell’intern pool (quindi nell’hashtable) e ne ritorna il riferimento se la stessa non è già presente, in caso contrario  ritorna sempicemente il riferimento ad essa. In questo modo è garantito che se due stringhe (o più) contengono gli stessi valori letterali sarà utilizzato (e quindi sarà presente in memoria) un unico riferimento, con evidente risparmio di risorse. Oltre a questo metodo, il metodo string.IsInterned ritorna il riferimento alla stringa nell’intern pool se presente, null in caso contrario.

L’intern pool è utile in opportuni scenari, ma ha un grosso svantaggio: poichè ogni stringa memorizzata nel pool è mantenuta all’interno di un hashtable interno del CLR, ne consegue che ogni stringa risulterà sempre raggiungibile dall’applicazione e quindi sopravviverà ad ogni garbage collector (poichè l’hashtable ne mantiene un riferimento). L’hashtable (e le stringhe in esso memorizzate) sarà distrutto e la memoria occupata reclamata solo quando il relativo appDomain (o processo) terminerà.

Come già detto, tutte le stringhe costanti sono inserite nell’intern pool automaticamente e quindi la memoria occupata non sarà reclamata fino a che il processo termina.  Questo comportamento è dimostrato dal seguente codice:

string a = “teststring”;
string b = “teststring”;
string x = string.IsInterned(a);
string y = string.IsInterned(b);
if (x != null)
    Console.WriteLine(“string a is interned”);
if (y != null)
    Console.WriteLine(“string b is interned”);
Console.WriteLine(object.ReferenceEquals(a,b));

L’output prodotto è il seguente:

string a in interned
string b is interned
true

Se questo comportamento non è quello desiderato è possibile utilizzare l’attributo a livello di assembly CompilationRelaxationsAttribute (namespace System.Runtime.CompilerServices), il quale permette disabilitare l’utilizzo dell’intern pool di stringhe da parte dell’assembly in cui l’attributo è definito, in questo modo:

[assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)]

Applicando questo attributo sarebbe lecito aspettarsi che l’intern pool di stringhe non sia più gestito. Invece non è così, nonostante la documentazione MSDN affermi il contrario. Sembra che questo attributo non abbia alcun effetto pratico se non negli assembly precompilati in codice nativo con l’utility NGEN. In altre parole, inserendo o non inserendo l’attributo, l’intern pool è comunque gestito dal CLR, a meno che non si precompili l’assembly con NGEN.

Come controprova di quanto affermato è possibile eseguire il codice sopraindicato con o senza l’attributo CompilationRelaxations. L’output sarà in ogni caso identico, e dimostrerà che le 2 stringhe sono inserite nell’intern pool.

Mi piacerebbe conoscere pareri di eventuali altri sviluppatori che hanno già riscontrato questo comportamento.