2009-11-04 8 views
11

Quiero ver cada carácter en una Cadena y pasar cada carácter de la Cadena como una Cadena a otra función.charAt() o subcadena? ¿Cual es mas rápido?

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(s.substring(i, i+1));} 

o

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(Character.toString(s.charAt(i)));} 

El resultado final debe ser una cadena. Entonces, ¿alguna idea que sea más rápida o más eficiente?

Respuesta

15

Como de costumbre: no importa, pero si insiste en pasar tiempo en la micro-optimización o si realmente te gusta para optimizar su caso de uso muy especial, intente esto:

import org.junit.Assert; 
import org.junit.Test; 

public class StringCharTest { 

    // Times: 
    // 1. Initialization of "s" outside the loop 
    // 2. Init of "s" inside the loop 
    // 3. newFunction() actually checks the string length, 
    // so the function will not be optimized away by the hotstop compiler 

    @Test 
    // Fastest: 237ms/562ms/2434ms 
    public void testCacheStrings() throws Exception { 
     // Cache all possible Char strings 
     String[] char2string = new String[Character.MAX_VALUE]; 
     for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) { 
      char2string[i] = Character.toString(i); 
     } 

     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       newFunction(char2string[s[i]]); 
      } 
     } 
    } 

    @Test 
    // Fast: 1687ms/1725ms/3382ms 
    public void testCharToString() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // Fast: Creates new String objects, but does not copy an array 
       newFunction(Character.toString(s.charAt(i))); 
      } 
     } 
    } 

    @Test 
    // Very fast: 1331 ms/ 1414ms/3190ms 
    public void testSubstring() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // The fastest! Reuses the internal char array 
       newFunction(s.substring(i, i + 1)); 
      } 
     } 
    } 

    @Test 
    // Slowest: 2525ms/2961ms/4703ms 
    public void testNewString() throws Exception { 
     char[] value = new char[1]; 
     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       value[0] = s[i]; 
       // Slow! Copies the array 
       newFunction(new String(value)); 
      } 
     } 
    } 

    private void newFunction(String string) { 
     // Do something with the one-character string 
     Assert.assertEquals(1, string.length()); 
    } 

} 
+0

Como esto va a pasar una cadena, necesita cambiar ligeramente las pruebas en la primera prueba. {char [] s = "abcdefg" .toCharArray();} debería estar dentro del bucle, o incluso mejor (para evitar una optimización inteligente por parte de la JVM, ponga todo el bucle y .toCharArray() dentro de una función separada). Es importante medir todos los gastos generales iniciales, así como los costos de los bucles. Especialmente dado que el rendimiento podría realmente dar una propina de uno a otro según la longitud de la cuerda. Por lo tanto, probar varias longitudes de picaduras también es importante. – MatBailie

+5

+1 por responder la pregunta. – gustafc

+0

Se movió "s" dentro del bucle y se agregó un assert() para evitar la optimización de JVM de newFunction(). Por supuesto ahora es más lento, pero las medidas relativas siguen siendo las mismas. Mi punto es simplemente que hay posibilidades de optimización si el problema se conoce exactamente. El objetivo no es cambiar qué función utilizar para una determinada operación, sino ver la operación en un nivel superior para obtener mejoras, p. mediante el almacenamiento en caché – mhaller

4

¿newFunction realmente necesita tomar un String? Sería mejor si usted podría hacer newFunction echar un char y lo llaman así:

newFunction(s.charAt(i)); 

De esa manera, se evita la creación de un objeto String temporal.

Para responder a su pregunta: es difícil decir cuál es más eficiente. En ambos ejemplos, se debe crear un objeto String que contenga solo un carácter. Lo que es más eficiente depende de cómo se implementan exactamente String.substring(...) y Character.toString(...) en su implementación de Java particular. La única forma de averiguarlo es ejecutar su programa a través de un generador de perfiles y ver qué versión usa más CPU y/o más memoria. Normalmente, no debe preocuparse por las micro optimizaciones como esta; solo dedique tiempo a esto cuando haya descubierto que esta es la causa de un problema de rendimiento y/o memoria.

+0

newFunction realmente necesita tomar una cuerda. Además de caracteres únicos, newFunction también maneja cadenas más largas también. Y los maneja de la misma manera. No quiero sobrecargar newFunction para tomar una char porque hace lo mismo en ambos casos. – estacado

+1

Estoy totalmente de acuerdo en que la micro-optimización debe evitarse en el desarrollo hasta que se considere necesario. También creo que, como ejercicio de aprendizaje, aprender sobre las asignaciones de memoria y otros "comportamientos ocultos" es muy importante. Personalmente estoy cansado de que los programadores naming caduquen el código corto en la creencia de que es corto = performant y, sin saberlo, usan algoritmos altamente ineficientes. Las personas que no aprenden esto = vago. Las personas que están obsesionadas por esto = lento. Hay un equilibrio para ser golpeado. En mi opinión :) – MatBailie

+0

@estacado: Si el rendimiento es su conductor (como implica su publicación) optimizar en los lugares correctos. Sobrecargar la nueva función para evitar los gastos indirectos de Cadena -puede ser la opción sensata dependiendo de cómo se vería la versión basada en [char]. Contorsionar su código alrededor de la función puede consumir más tiempo, ser menos efectivo y menos sostenible. – MatBailie

15

La respuesta es: it doesn't matter.

Perfile su código. ¿Es este tu cuello de botella?

+0

¿Perfil de qué manera? Para el uso de memoria? –

0

Primero obtendría el carácter subyacente [] de la cadena fuente usando String.toCharArray() y luego procedo a llamar a newFunction.

Pero estoy de acuerdo con Jesper que sería mejor si pudiera hacer frente a los personajes y evitar todas las funciones de cadena ...

+0

String.charAt (i) hace esa búsqueda hasta donde yo sé. Copiando la cadena a una nueva matriz (que es lo que entiendo String.toCharArray() por hacer) introduce una sobrecarga nueva y diferente. ¿Pasa repetidamente una referencia de cadena a charAt() más lenta que la conversión a una matriz nativa primero? Sospecho que depende de la longitud de la cadena ... – MatBailie

+0

Siempre hay intercambios :) Solo el OP realmente puede decir qué es más eficiente. –

2

De los dos fragmentos que has publicado, no me gustaría a decir. Estoy de acuerdo con Will en que es casi irrelevante en el rendimiento general de su código, y si no lo es, puede hacer el cambio y determinar por sí mismo cuál es el más rápido para sus datos con su JVM en su hardware.

Dicho esto, es probable que el segundo fragmento sea mejor si convierte primero la Cadena en una matriz de caracteres, y luego realiza sus iteraciones sobre la matriz. Si lo hiciera de esta manera, realizaría la tara de una sola vez (convirtiendo a la matriz) en lugar de cada llamada. Además, puede pasar la matriz directamente al constructor de cadenas con algunos índices, que es más eficiente que tomar un de una matriz para pasarlo individualmente (que luego se convierte en una matriz de un carácter):

String s = "abcdefg"; 
char[] chars = s.toCharArray(); 
for(int i = 0; i < chars.length; i++) { 
    newFunction(String.valueOf(chars, i, 1)); 
} 

Pero para reforzar mi primer punto, cuando miras lo que realmente estás evitando en cada llamada de String.charAt(), son dos controles de límites, un (booleano) booleano O, y una adición. Esto no hará ninguna diferencia notable. Tampoco es la diferencia en los constructores de cadenas.

Básicamente, ambos modismos están bien en términos de rendimiento (ninguno de los dos es inmediatamente ineficiente) por lo que no deberías perder más tiempo trabajando en ellos a menos que un generador de perfiles demuestre que esto consume una gran parte del tiempo de ejecución de la aplicación.Y aun así, es casi seguro que obtendrás más mejoras de rendimiento al reestructurar tu código de soporte en esta área (por ejemplo, tener newFunction tomar toda la cadena en sí); java.lang.String está bastante bien optimizado en este punto.

+0

'substring' en el jvm actual realmente usa el conjunto de caracteres original como almacén de respaldo, mientras está iniciando una copia. Así que mi intuición dice que la subcadena en realidad será más rápida, ya que una memcpy probablemente sea más costosa (dependiendo de qué tan grande sea la cadena, más grande es mejor). – wds

Cuestiones relacionadas