jueves, 3 de junio de 2010

operador +, cadenas, cadenas constante y el compilador en C#

Al igual que en java, el compilador de C# es capaz de realizar optimizaciones al momento de compilar el codigo y convertir una instruccion en otra, utilizando las funciones definidas en la clase String.

Como se vio en el post dedicado a java, es posible utilizar el compilador a nuestro favor, cuando se conoce como funciona.
En el siguiente ejemplo, se muestra una concatenacion, utilizando el operador +.

01: using System;
02: using System.Collections.Generic;
03: using System.Linq;
04: using System.Text;
05: 
06: namespace ClassLibrary1
07: {
08:     public class Class1
09:     {
10:         void Test()
11:         {
12:             string a = "cadena1";
13:             string b = "cadena2";
14:             string x = a + b;
15:             Console.WriteLine(x);
16:         }
17:     }
18: }
19: 

Al decompilar el codigo, empleando el comando ildasm, se obtiene el siguiente codigo:

.method private hidebysig instance void  Test() cil managed
   {
     // Code size       29 (0x1d)
     .maxstack  2
     .locals init ([0] string a,
              [1] string b,
              [2] string x)
     IL_0000:  nop
     IL_0001:  ldstr      "cadena1"
     IL_0006:  stloc.0
     IL_0007:  ldstr      "cadena2"
     IL_000c:  stloc.1
     IL_000d:  ldloc.0
     IL_000e:  ldloc.1
     IL_000f:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
     IL_0014:  stloc.2
     IL_0015:  ldloc.2
     IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)
     IL_001b:  nop
     IL_001c:  ret
   } // end of method Class1::Test

El codigo que genera codigo similar es el siguiente:


01: using System;
02: using System.Collections.Generic;
03: using System.Linq;
04: using System.Text;
05: 
06: namespace ClassLibrary1
07: {
08:     public class Class2
09:     {
10:         void Test()
11:         {
12:             string x = String.Concat("cadena1","cadena2");
13:             Console.WriteLine(x);
14:         }
15:     }
16: }
17: 

Como es posible observar, el codigo generado ejecuta una funcion propia de la clase String, la cual sabe concatenar cadenas, generando una nueva cadena del tamaño necesario. Sin embargo, este comportamiento solo se presenta cuando se emplean 2, 3 y hasta 4 cadenas, ya cuando se trata de 5 o mas cadenas, primero construye un arreglo de cadenas, con las cadenas a concatenar y posteriormente emplea el metodo Concat(String[]). Por ejemplo:


01: using System;
02: using System.Collections.Generic;
03: using System.Linq;
04: using System.Text;
05: 
06: namespace ClassLibrary1
07: {
08:     class Class3
09:     {
10:         void Test()
11:         {
12:             string a = "cadena1";
13:             string b = "cadena2";
14:             string c = "cadena3";
15:             string d = "cadena4";
16:             string e = "cadena5";
17:             string x = a + b + c + d + e;
18:             Console.WriteLine(x);
19:         }
20:     }
21: }
22: 

Produciendo el siguiente codigo:

.method private hidebysig instance void  Test() cil managed
  {
    // Code size       84 (0x54)
    .maxstack  3
    .locals init ([0] string a,
             [1] string b,
             [2] string c,
             [3] string d,
             [4] string e,
             [5] string x,
             [6] string[] CS$0$0000)
    IL_0000:  nop
    IL_0001:  ldstr      "cadena1"
    IL_0006:  stloc.0
    IL_0007:  ldstr      "cadena2"
    IL_000c:  stloc.1
    IL_000d:  ldstr      "cadena3"
    IL_0012:  stloc.2
    IL_0013:  ldstr      "cadena4"
    IL_0018:  stloc.3
    IL_0019:  ldstr      "cadena5"
    IL_001e:  stloc.s    e
    IL_0020:  ldc.i4.5
    IL_0021:  newarr     [mscorlib]System.String
    IL_0026:  stloc.s    CS$0$0000
    IL_0028:  ldloc.s    CS$0$0000
    IL_002a:  ldc.i4.0
    IL_002b:  ldloc.0
    IL_002c:  stelem.ref
    IL_002d:  ldloc.s    CS$0$0000
    IL_002f:  ldc.i4.1
    IL_0030:  ldloc.1
    IL_0031:  stelem.ref
    IL_0032:  ldloc.s    CS$0$0000
    IL_0034:  ldc.i4.2
    IL_0035:  ldloc.2
    IL_0036:  stelem.ref
    IL_0037:  ldloc.s    CS$0$0000
    IL_0039:  ldc.i4.3
    IL_003a:  ldloc.3
    IL_003b:  stelem.ref
    IL_003c:  ldloc.s    CS$0$0000
    IL_003e:  ldc.i4.4
    IL_003f:  ldloc.s    e
    IL_0041:  stelem.ref
    IL_0042:  ldloc.s    CS$0$0000
    IL_0044:  call       string [mscorlib]System.String::Concat(string[])
    IL_0049:  stloc.s    x
    IL_004b:  ldloc.s    x
    IL_004d:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0052:  nop
    IL_0053:  ret
  } // end of method Class3::Test


En cambio, el compilador de C# al igua que el de java, es capaz de detectar cadenas constantes "inline" y generar una cadena con el resultado en tiempo de compilacion, evitando el procesamiento en tiempo de ejecucion


01: using System;
02: using System.Collections.Generic;
03: using System.Linq;
04: using System.Text;
05: 
06: namespace ClassLibrary1
07: {
08:     public class Class4
09:     {
10:         void Test()
11:         {
12:             string x = "cadena1" + "cadena2";
13:             Console.WriteLine(x);
14:         }
15:     }
16: }
17: 

Generando el siguiente codigo:

.method private hidebysig instance void  Test() cil managed
{
  // Code size       15 (0xf)
  .maxstack  1
  .locals init ([0] string x)
  IL_0000:  nop
  IL_0001:  ldstr      "cadena1cadena2"
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000d:  nop
  IL_000e:  ret
} // end of method Class4::Test

El compilador puede ayudar en el desempeño del sistema, al generar algunas de estas operaciones previamente.

miércoles, 2 de junio de 2010

cadenas en java (strings)

En java, como en muchos lenguajes de programacion modernos, existe una clase que define la representacion interna de una cadena durante la ejecucion del programa. En java esta clase es String. Internamente, la clase String, esta representada por un arreglo del tipo nativo char. Derivado de esto, la maquina virtual de java, maneja las cadenas como un conjunto de caracteres codificados en Unicode en formato UTF-16 (No UTF-8, no ASCII). Una consecuencia es que cada caracter que conforma la cadena, utiliza 2 bytes.

Una situacion que se da debido a la restriccion de inmutabilidad, es que los constructores que reciben un arreglo de caracteres (char[]) deben generar un arreglo interno nuevo y copiar el original (total o parcialmente, segun sea el caso), dependiendo del constructor que se emplee.

En cambio, cuando se genera una cadena utilizando otra cadena como base, es posible compartir el arreglo internamente, pues la fuente inmutable por definicion. En algunas situaciones, se puede generar un nuevo arreglo interno, para evitar el uso de memoria.

Cuando se necesita extraer un caracter de una cadena, es preferible utilizar la funcion charAt, en vez de un substring. Dado que esta funcion regresa un valor directamente (el iesimo caracter en el arreglo) sin necesidad de crear objetos y utilizando exclusivamente el stack, en cambio substring crea un nuevo objeto String, que puede compartir la memoria empleada por la cadena original. Una prueba de esto, es ejecutar el siguiente codigo:

        String s = "test";
        long start = System.currentTimeMillis();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            s.substring(0,1);
        }
        System.out.println(System.currentTimeMillis() - start);

Contra este codigo

        String s = "test";
        long start = System.currentTimeMillis();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            s.charAt(0);
        }
        System.out.println(System.currentTimeMillis() - start);

El primer codigo, puede llegar a ejecutarse en unos cuantos milisegundos (30 aprox en un Centrino Duo a 1.83GHz), mientras que el segundo codigo se tarda mas de 200 veces mas tiempo (casi 1 minuto en el mismo procesador), particularmente, cuando se ejecuta la maquina virtual con la opcion -server, cuando se ejecuta con la opcion -client, los tiempos de ejecucion del primer ejemplo es de 1/3 con respecto al segundo ejemplo. Este codigo optimizado esta empleado en el post "Extraccion del nombre de una propiedad del getter respectivo en java".

La funcion concat, es optima cuando se emplea exclusivamene con 2 cadenas, pues se aloja exactamente la memoria necesaria para construir la nueva cadena, cuya longitud es la suma de ambas longitudes.