2010-11-30 16 views
5

¿Qué tan rápido puedo reemplazar caracteres en una cadena?Reemplazo de caracteres en cadenas en VB.NET

El trasfondo de esta pregunta es este: tenemos un par de aplicaciones que se comunican entre sí y con las aplicaciones de los clientes a través de sockets. Estos mensajes de socket contienen caracteres no imprimibles (por ejemplo, chr (0)) que necesitan ser reemplazados por una cadena predeterminada (por ejemplo, "{Nul}"}, porque los mensajes de socket se guardan en un archivo de registro. En una nota lateral, no cada mensaje de registro tendrá que tener caracteres reemplazados.

Ahora se ubicó en esta pequeña aventura de lectura de this MSDN link que encontré de un puesto diferente de este sitio.

el método actual se utilizó en el ... principio del día ... estaba usando StringBuilder para buscar todos los reemplazos posibles, como ...

Public Function ReplaceSB(ByVal p_Message As String) As String 
     Dim sb As New System.Text.StringBuilder(p_Message) 

     sb.Replace(Chr(0), "{NUL}") 
     sb.Replace(Chr(1), "{SOH}") 

     Return sb.ToString 
    End Function 

Ahora, como indica la publicación del blog, dejar StringBuilder fuera y usar string.replace produce resultados más rápidos. (En realidad, el uso de StringBuilder era el método más lento de hacer esto durante todo el día.)

p_Message = p_Message.Replace(Chr(0), "{NUL}") 
    p_Message = p_Message.Replace(Chr(1), "{SOH}") 

Sabiendo que no necesitaría cada mensaje que pasar por este proceso que pensé que sería ahorrar tiempo al no tener que procesar esos mensajes que podría ser dejado afuera. Entonces, al usar expresiones regulares, primero busqué la cadena y luego determiné si necesitaba ser procesada o no. Esto era más o menos lo mismo que usar el string.replace, básicamente un lavado al guardar el tiempo de no procesar todas las cadenas, pero perder tiempo de revisarlas todas con expresiones regulares.

Luego se sugirió probar el uso de algunas matrices que coinciden con sus índices con las antiguas y las nuevas y usarlas para procesar los mensajes. Entonces sería algo como esto ...

Private chrArray() As Char = {Chr(0), Chr(1)} 
Private strArray() As String = {"{NUL}", "{SOH}"} 

Public Function TestReplace(ByVal p_Message As String) As String 
    Dim i As Integer 

    For i = 0 To ((chrArray.Length) - 1) 
     If p_Message.Contains(chrArray(i).ToString) Then 
      p_Message = p_Message.Replace(chrArray(i), strArray(i)) 
     End If 
    Next 

    Return p_Message 
End Function 

Hasta ahora, esta ha sido la forma más rápida que he encontrado para procesar estos mensajes. He intentado varias otras formas de resolver esto, así como convertir la cadena entrante en una matriz de caracteres y compararla con también intentar recorrer la cadena en lugar de chrArray.

Así que mi pregunta a todos es: ¿Puedo hacer esto más rápido? ¿Qué me estoy perdiendo?

+0

Si usted tiene la opción de utilizar C#, que podría ser capaz de escribir una función perversamente rápido con código no seguro. – Juliet

+0

Me había preguntado si eso hubiera sido posible, pero pronto conté esa opción debido a los parámetros que me dieron para trabajar con ... oh bien – Tim

Respuesta

1

Es posible que pueda exprimir un poco más de velocidad reduciendo algunas búsquedas. Tomemos, por ejemplo esto:

If p_Message.Contains(chrArray(i).ToString) Then 

El .Contains método es O (n). En el peor de los casos, vas a atravesar todos los caracteres en toda la cadena sin encontrar nada, por lo que esperas atravesar al menos una vez para cada carácter en tu matriz, por lo que es O (nm) donde n es la longitud de su cadena ym es la cantidad de caracteres que está reemplazando.

Es posible conseguir un poco mejor rendimiento de la siguiente manera (mi VB-fu es oxidado, no se ha probado;)):

Private Function WriteToCharList(s as String, dest as List(Of Char)) 
    for each c as Char in s 
     dest.Add(c) 
    Next 
End Function 

Public Function TestReplace(ByVal p_Message As String) As String 
    Dim chars as new List(Of Char)(p_Message.Length) 

    For each c as Char in p_Message 
     Select Case c 
      Case Chr(0): WriteToCharList("{NUL}", chars) 
      Case Chr(1): WriteToCharList("{SOH}", chars) 
      Case Else: chars.Add(c); 
     End Select 
    Next 

    Return New String(chars) 
End Function 

Este atravesará caracteres en p_Message como máximo dos veces (una vez para atravesar , una vez cuando el constructor de cadenas copia la matriz de caracteres), haciendo que esta función sea O (n).

+1

Tenga en cuenta que estoy inicializando el tamaño de la lista 'chars'. La colección 'List ' es básicamente una envoltura alrededor de una matriz, y si agrega más elementos de los que la matriz puede contener, redimensiona la matriz para doblar su tamaño, por lo que agregar una sola marca es el peor caso O (n), promedio caso O (1).Si puede estimar el número de caracteres Chr (0) y Chr (1) que tiene, o simplemente asigna un búfer lo suficientemente grande, puede mejorar el rendimiento un poco al evitar el cambio de tamaño de la matriz ocasionalmente cuando la cantidad de caracteres que está agregando excede la longitud de tu matriz de caracteres. – Juliet

+0

Gracias, Julieta. Le di a cada respuesta una prueba o dos y esta fue la más rápida. Bueno, la solución final no era exactamente así, la idea básica sigue siendo la misma. Me sorprendió un poco, por alguna razón no pensé que el caso seleccionado sería tan rápido, pero lo es. – Tim

0

StringBuilder ofrece la función Replace() más rápida en .NET.

+0

La función de reemplazo de StringBuilder() fue la más lenta que he intentado durante todo el día. Al procesar a través de 240 mensajes de registro, StringBuilder fue aproximadamente 1,8 milisegundos más lento que la última forma de procesar los mensajes de mi OP. – Tim

+0

"Depende" es la respuesta correcta aquí. Realmente no se puede garantizar que StringBuilder sea más rápido sin realmente crear perfiles. – Juliet

0

Un par de notas generales aquí:

  1. que podría ser capaz de mejorar la función de búsqueda mediante el uso de una llanura .IndexOf() o .Contains() búsqueda, ya que sólo está buscando caracteres individuales.
  2. Es posible que pueda mejorar su rendimiento total devolviendo el objeto StringBuilder desde su función directamente y proporcionando sobrecargas para otras funciones que acepten constructores de cadenas como entrada o llamando a .ToString() en algún momento posterior del proceso (nota: También puede llamar a .ToString() en objetos que ya son cadenas)
  3. Definitivamente, debe poder mejorar aún más el rendimiento/rendimiento mediante el uso de un StringReader/TextReader en la cadena y continuar tratando todo como transmisiones que siguen pasando por la cadena .

Al menos puede modificar su método final de esta manera:

Public Function TestReplace(ByVal p_Message As String) As String 
    Static chrArray() As Char = {ChrW(0), ChrW(1)} 
    Static strArray() As String = {"{NUL}", "{SOH}"} 

    Dim rdr As New StringReader(p_Message) 
    Dim result As New StringWriter() 

    Dim i As Integer 
    While (i = rdr.Read()) <> -1 
     Dim c As Char = ChrW(i) 
     Dim index As Integer = Array.IndexOf(chrArray, c) 
     If index >= 0 Then result.Write(strArray(index)) Else result.Write(c) 
    End While 

    Return result.ToString() 
End Function 

Nota que sus puntos de referencia dependerán en gran medida del tipo de cuerdas que está lanzando en ella, por lo que asegúrese de que está utilizando la muestra más representativa (y debe ser una muestra de buen tamaño) posible.

+0

Jugué con diferentes maneras de usar .IndexOf() y .Contains() sin obtener ningún resultado, por lo general alrededor de una décima o dos de un milisegundo más lento. Voy a intentar algunas pruebas más con su número 2 y 3 ... Publicaré los resultados – Tim

+0

@Tim - vea mi respuesta actualizada –

+0

Estoy usando una lista de 240 mensajes de registro sin formato para cada prueba. Elijo este conjunto basado en una carga de trabajo más pesada que la media para la función en cuestión. Todo en cada prueba es exactamente igual excepto para la función que reemplaza. – Tim

0

Echa un vistazo a este example. Tiene algunas estadísticas de referencia que comparan los dos métodos.

+0

Interesante leer, pero los resultados que he visto han sido opuestos a eso. StringBuilder, al menos en mis pruebas, ha sido una de las opciones más lentas. – Tim

0

Esto también debería ser más rápido:

Private Shared strList As New Dictionary(Of Char, String) 

    Shared Sub New() 
     strList.Add(Chr(0), "{NUL}") 
     strList.Add(Chr(1), "{SOH}") 
    End Sub 

    Public Function TestReplace(ByVal p_Message As String) As String 
     For Each c As Char In strList.Keys 
      If p_Message.IndexOf(c) <> -1 Then 
       p_Message = p_Message.Replace(c, strList(c)) 
      End If 
     Next 

     Return p_Message 
    End Function 
+2

Tenga en cuenta que, si bien es preciso desde el punto de vista de BigOn, en la práctica esto solo es más rápido cuando el tamaño de su diccionario aumenta un poco: al menos 10 elementos o más. –

+0

Esto fue definitivamente más rápido. En realidad, esto cortó aproximadamente 2 milisegundos de lo que está en su lugar ahora utilizando los mismos datos de prueba de 240 mensajes sin formato, pero este no fue el más rápido en todas mis pruebas, lo que me sorprendió ... Gracias por la ayuda – Tim

Cuestiones relacionadas