c# - Interno de cadenas en.Net Framework: cuáles son los beneficios y cuándo usar el interning
string performance (5)
Quiero conocer el proceso y los aspectos internos del internamiento de cadenas específico de .Net framework . También me gustaría saber los beneficios de usar el interinato y los escenarios / situaciones en los que deberíamos usar el interinato de cuerdas para mejorar el rendimiento. Aunque estudié prácticas en el libro CLR de Jeffery Richter, todavía estoy confundido y me gustaría saberlo con más detalle.
[Editar] para hacer una pregunta específica con un código de muestra como a continuación:
private void MethodA()
{
string s = "String"; // line 1 - interned literal as explained in the answer
//s.intern(); // line 2 - what would happen in line 3 if we uncomment this line, will it make any difference?
}
private bool MethodB(string compareThis)
{
if (compareThis == "String") // line 3 - will this line use interning (with and without uncommenting line 2 above)?
{
return true;
}
return false;
}
En general, la internación es algo que simplemente sucede, automáticamente, cuando se usan valores de cadena literales. El internado proporciona el beneficio de tener solo una copia del literal en memoria, sin importar la frecuencia con que se use.
Dicho esto, es raro que haya una razón para internar sus propias cadenas que se generan en el tiempo de ejecución, o incluso pensar en el interrogatorio de cadenas para el desarrollo normal.
Existen algunos beneficios potenciales si va a trabajar mucho con comparaciones de cadenas generadas en tiempo de ejecución potencialmente idénticas (ya que la internación puede acelerar las comparaciones a través de ReferenceEquals). Sin embargo, este es un uso altamente especializado, y requeriría una buena cantidad de perfiles y pruebas, y no sería una optimización que consideraría a menos que hubiera un problema medido.
Esta es una pregunta "vieja", pero tengo un ángulo diferente sobre ella.
Si vas a tener muchas cadenas de larga duración desde un grupo pequeño , la internación puede mejorar la eficiencia de la memoria.
En mi caso, estaba intercediendo en otro tipo de objeto en un diccionario estático porque se reutilizaban con frecuencia, y esto sirvió como un caché rápido antes de persistir en el disco.
La mayoría de los campos en estos objetos son cadenas, y el conjunto de valores es bastante pequeño (mucho más pequeño que el número de instancias, de todos modos).
Si se tratara de objetos transitorios, no importaría porque los campos de cuerda serían basura recolectada a menudo. Pero debido a que se mantenían referencias a ellos, su uso de memoria comenzó a acumularse (incluso cuando no se estaban agregando nuevos valores únicos).
Así que el internamiento de los objetos redujo sustancialmente el uso de la memoria, y también lo hizo el internar sus valores de cadena mientras estaban siendo internados.
La internación es un detalle de implementación interna . A diferencia del boxeo , no creo que haya ningún beneficio en saber más de lo que has leído en el libro de Richter.
Los beneficios de microoptimización de las cadenas de prácticas de forma manual son mínimos, por lo que generalmente no se recomiendan.
Esto probablemente lo describe:
class Program
{
const string SomeString = "Some String"; // gets interned
static void Main(string[] args)
{
var s1 = SomeString; // use interned string
var s2 = SomeString; // use interned string
var s = "String";
var s3 = "Some " + s; // no interning
Console.WriteLine(s1 == s2); // uses interning comparison
Console.WriteLine(s1 == s3); // do NOT use interning comparison
}
}
La internalización de cadenas afecta el consumo de memoria.
Por ejemplo, si lee cadenas y las guarda en una lista para el almacenamiento en caché; y la misma cadena exacta ocurre 10 veces, la cadena se almacena realmente solo una vez en la memoria si se usa string.Intern. Si no, la cadena se almacena 10 veces.
En el ejemplo siguiente, la variante string.Intern consume aproximadamente 44 MB y la versión sin (uncommented) consume 1195 MB.
static void Main(string[] args)
{
var list = new List<string>();
for (int i = 0; i < 5 * 1000 * 1000; i++)
{
var s = ReadFromDb();
list.Add(string.Intern(s));
//list.Add(s);
}
Console.WriteLine(Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024 + " MB");
}
private static string ReadFromDb()
{
return "abcdefghijklmnopqrstuvyxz0123456789abcdefghijklmnopqrstuvyxz0123456789abcdefghijklmnopqrstuvyxz0123456789" + 1;
}
La internalización también mejora el rendimiento para equals-compare. El ejemplo debajo de la versión interna toma aproximadamente 1 unidades de tiempo, mientras que el no interno toma 7 unidades de tiempo.
static void Main(string[] args)
{
var a = string.Intern(ReadFromDb());
var b = string.Intern(ReadFromDb());
//var a = ReadFromDb();
//var b = ReadFromDb();
int equals = 0;
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 250 * 1000 * 1000; i++)
{
if (a == b) equals++;
}
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed + ", equals: " + equals);
}
Las cadenas internas tienen las siguientes características:
- Dos cadenas internas idénticas tendrán la misma dirección en la memoria.
- La memoria ocupada por cadenas internas no se libera hasta que finaliza su aplicación.
- Internar una cadena implica calcular un hash y buscarlo en un diccionario que consume ciclos de CPU.
- Si hay varios hilos que internan cadenas al mismo tiempo, se bloquearán entre sí porque los accesos al diccionario de cadenas internas se serializan.
Las consecuencias de estas características son:
Puede probar dos cadenas internas para la igualdad simplemente comparando el puntero de dirección que es mucho más rápido que comparar cada carácter en la cadena. Esto es especialmente cierto si las cadenas son muy largas y comienzan con los mismos caracteres. Puede comparar cadenas internas con el método
Object.ReferenceEquals
, pero es más seguro usar el operadorstring ==
porque comprueba si las cadenas son en primer lugar de Internet.Si usa la misma cadena muchas veces en su aplicación, su aplicación solo almacenará una copia de la cadena en la memoria, reduciendo la memoria requerida para ejecutar su aplicación.
Si interviene en muchas cadenas diferentes, esto asignará memoria para aquellas cadenas que nunca serán liberadas, y su aplicación consumirá cantidades cada vez mayores de memoria.
Si tiene una gran cantidad de cadenas internas, el internamiento de cadenas puede ser lento y los hilos se bloquearán entre sí cuando accedan al diccionario de cadenas interno.
Deberías usar el interinato de cuerdas solo si:
- El conjunto de cadenas que estás intercediendo es bastante pequeño.
- Usted compara estas cadenas muchas veces por cada vez que las interna.
- Realmente te importan las optimizaciones de rendimiento minuto.
- No tienes muchos hilos de cadenas de prácticas agresivamente.