2011-06-29 14 views
65

Tengo una cadena en la que necesito reemplazar marcadores con valores de un diccionario. Tiene que ser lo más eficiente posible. Hacer un loop con un string.replace va a consumir memoria (las cadenas son inmutables, recuerda). ¿Sería mejor StringBuilder.Replace() ya que está diseñado para trabajar con manipulaciones de cadenas?String.Replace() vs. StringBuilder.Replace()

Tenía la esperanza de evitar el gasto de RegEx, pero si eso va a ser más eficiente, entonces que así sea.

Nota: No me importa la complejidad del código, solo qué tan rápido se ejecuta y la memoria que consume.

Estadísticas promedio: 255-1024 caracteres de longitud, 15-30 teclas en el diccionario.

+0

¿Cuál es el patrón (longitudes) de los marcadores y valores? –

+0

Corto. Marcadores 5-15, valores 5-25 –

+0

Posible duplicado de http://stackoverflow.com/questions/287842/is-stringbuilder-replace-more-efficient-than-string-replace –

Respuesta

60

Usando Redgate Profiler usando el siguiente código

class Program 
    { 
     static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; 
     static Dictionary<string, string> values; 

     static void Main(string[] args) 
     { 
      Console.WriteLine("Data length: " + data.Length); 
      values = new Dictionary<string, string>() 
      { 
       { "ab", "aa" }, 
       { "jk", "jj" }, 
       { "lm", "ll" }, 
       { "yz", "zz" }, 
       { "ef", "ff" }, 
       { "st", "uu" }, 
       { "op", "pp" }, 
       { "x", "y" } 
      }; 

      StringReplace(data); 
      StringBuilderReplace1(data); 
      StringBuilderReplace2(new StringBuilder(data, data.Length * 2)); 

      Console.ReadKey(); 
     } 

     private static void StringReplace(string data) 
     { 
      foreach(string k in values.Keys) 
      { 
       data = data.Replace(k, values[k]); 
      } 
     } 

     private static void StringBuilderReplace1(string data) 
     { 
      StringBuilder sb = new StringBuilder(data, data.Length * 2); 
      foreach (string k in values.Keys) 
      { 
       sb.Replace(k, values[k]); 
      } 
     } 

     private static void StringBuilderReplace2(StringBuilder data) 
     { 
      foreach (string k in values.Keys) 
      { 
       data.Replace(k, values[k]); 
      } 
     } 
    } 
  • String.Replace = 5.843ms
  • StringBuilder.Replace # 1 = 4.059ms
  • Stringbuilder.Replace # 2 = 0.461ms

longitud de cadena = 1,456

StringBuilder # 1 crea el StringBuilder en el método, mientras que # 2 no por lo que la diferencia de rendimiento va a terminar siendo el mismo más probable, ya que estás en movimiento que el trabajo fuera del método. Si comienza con un generador de cuerdas en lugar de una cadena, entonces el # 2 podría ser el camino a seguir.

En cuanto a la memoria, el uso de perfiles RedGateMemory, no hay nada de qué preocuparse hasta que llegue a muchos sustituir operaciones en las que StringBuilder va a ganar en general.

+0

Si el compilador optimizara esto, ¿cambiaría la línea de StringBuilderReplace1 (data)? a StringBuilderReplace2 (nuevo StringBuilder (data, data.Length * 2));? Sólo curioso. Entiendo la diferencia, era curioso si lo sabías. – pqsk

+0

No entiendo por qué el método SB 2 es mucho más rápido: el JIT debe optimizar tanto SB # 1 como SB # 2 para que sean iguales en tiempo de ejecución. – Dai

+0

@Dai tenga en cuenta que esto fue en 2011. Las cosas pueden haber cambiado desde entonces. –

9

Esto puede ser de ayuda:

http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx

La respuesta corta parece ser que String.Replace es más rápido, aunque puede tener un mayor impacto en la huella de la memoria/de basura recogida sobrecarga.

+0

interesante. En base a su prueba, string.replace was better. Estaba pensando que debido al pequeño tamaño de la cadena de cuerdas.reemplazar sería mejor considerar cualquier sobrecarga de crear un generador de cadenas –

5

¿Podría ser cualquier stringbuilder.replace mejor [que String.Replace]

Sí, mucho mejor. Y si puede estimar un límite superior para la nueva cadena (parece que puede), entonces probablemente sea lo suficientemente rápido.

Cuando crea que tiene gusto:

var sb = new StringBuilder(inputString, pessimisticEstimate); 

entonces el StringBuilder no tendrá que volver a asignar su memoria intermedia.

6

Sí, StringBuilder le dará tanto el aumento en la velocidad y la memoria (básicamente porque no va a crear una instancia de una cadena cada vez que se va a hacer una manipulación con ella - StringBuilder funciona siempre con el mismo objeto). Aquí hay un MSDN link con algunos detalles.

+0

, pero ¿vale la pena la sobrecarga de crear el generador de cadenas? –

+1

@Dustin: con 15-30 reemplazos probablemente sea. –

+0

+1 porque vale la pena si tiene muchas operaciones. –

1

La conversión de datos de una Cadena a un StringBuilder y de vuelta tomará algún tiempo. Si uno solo está realizando una única operación de reemplazo, este tiempo puede no ser recuperado por las mejoras de eficiencia inherentes a StringBuilder. Por otro lado, si uno convierte una cadena a un StringBuilder, luego realiza muchas operaciones de Reemplazar y la convierte de nuevo al final, el enfoque de StringBuilder tiende a ser más rápido.

1

En lugar de ejecutar 15-30 reemplazar operaciones en toda la cadena, podría ser más eficiente usar algo así como una estructura de datos trie para contener su diccionario. Luego puede recorrer la cadena de entrada una vez para hacer toda su búsqueda/reemplazo.

1

Dependerá mucho de cuántos marcadores estén presentes en una cadena determinada en promedio.

rendimiento de la búsqueda de una clave es probable que sea similar entre StringBuilder y String, StringBuilder, pero va a ganar si tiene que reemplazar muchos marcadores en una sola cadena.

Si sólo está prevista una o dos marcadores por cadena en promedio, y su diccionario es pequeño, sólo iría por la String.Replace.

Si hay muchos marcadores, es posible que desee definir una sintaxis personalizada para identificar los marcadores, p. encierro entre llaves con una regla de escape adecuada para una llave literal. A continuación, puede implementar un algoritmo de análisis que itera por los caracteres de la cadena una vez, reconociendo y reemplazando cada marcador que encuentra. O usa una expresión regular.

+0

+1 para expresiones regulares: tenga en cuenta que si esto se hace, el reemplazo real puede usar un 'MatchEvaluator' para hacer la búsqueda del diccionario. – Random832

1

Mis dos centavos por aquí, que acabo de escribir par de líneas de código para probar cómo cada método lleva a cabo y, como era de esperar, resultado es "depende".

Para cadenas más largas Regex parece funcionar mejor, para cuerdas más cortas, String.Replace es. Puedo ver que el uso de StringBuilder.Replace no es muy útil, y si se usa incorrectamente, podría ser letal en la perspectiva de GC (traté de compartir una instancia de StringBuilder).

Compruebe mi StringReplaceTests GitHub repo.

1

El problema con la respuesta de @DustinDavis es que opera recursivamente en la misma cadena. A menos que planee realizar un tipo de manipulación de ida y vuelta, en este tipo de prueba debería tener objetos separados para cada caso de manipulación.

Decidí crear mi propia prueba porque encontré algunas respuestas contradictorias en toda la Web, y quería estar completamente seguro. El programa en el que estoy trabajando se ocupa de una gran cantidad de texto (archivos con decenas de miles de líneas en algunos casos).

Así que aquí hay un método rápido que puedes copiar y pegar y ver por ti mismo que es más rápido. Puede que tenga que crear su propio archivo de texto para probar, pero puede copiar y pegar texto desde cualquier lugar y hacer un archivo bastante grande por sí mismo:

using System; 
using System.Diagnostics; 
using System.IO; 
using System.Text; 
using System.Windows; 

void StringReplace_vs_StringBuilderReplace(string file, string word1, string word2) 
{ 
    using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) 
    using(StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8)) 
    { 
     string text = streamReader.ReadToEnd(), 
       @string = text; 
     StringBuilder @StringBuilder = new StringBuilder(text); 
     int iterations = 10000; 

     Stopwatch watch1 = new Stopwatch.StartNew(); 
     for(int i = 0; i < iterations; i++) 
      if(i % 2 == 0) @string = @string.Replace(word1, word2); 
      else @string = @string.Replace(word2, word1); 
     watch1.Stop(); 
     double stringMilliseconds = watch1.ElapsedMilliseconds; 

     Stopwatch watch2 = new Stopwatch.StartNew(); 
     for(int i = 0; i < iterations; i++) 
      if(i % 2 == 0) @StringBuilder = @StringBuilder .Replace(word1, word2); 
      else @StringBuilder = @StringBuilder .Replace(word2, word1); 
     watch2.Stop(); 
     double StringBuilderMilliseconds = watch1.ElapsedMilliseconds; 

     MessageBox.Show(string.Format("string.Replace: {0}\nStringBuilder.Replace: {1}", 
             stringMilliseconds, StringBuilderMilliseconds)); 
    } 
} 

Tengo que String.Replace() fue más rápido en alrededor 20% cada vez intercambiando palabras de 8-10 letras. Inténtalo por ti mismo si quieres tu propia evidencia empírica.

Cuestiones relacionadas