Mostrando las entradas con la etiqueta operadores. Mostrar todas las entradas
Mostrando las entradas con la etiqueta operadores. Mostrar todas las entradas

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.

viernes, 28 de mayo de 2010

numeros constantes y el operador + en java

Al igual que ocurre con las cadenas, el compilador, puede realizar calculos y utilizar el resultado, reemplazando una complicada operacion y minimizando el trabajo en tiempo de ejecucion. Esto resultando en menor uso de memoria y procesador. El ejemplo acontinuacion:

01: package test;
02: 
03: public class NumberConstantTest {
04:         public static void main(String[] args) {
05:                 int a = 1 // number or hours
06:                 * 60 // mins per hour
07:                 * 1000; // milliseconds per second
08:                 float b = (3.5f + 1.2f) / 2f;
09:                 double c = a * (1d / 3d);
10:                 System.out.println(a);
11:                 System.out.println(b);
12:                 System.out.println(c);
13:         }
14: }
15: 

Explicacion
05-07: Un ejemplo de como definir el numero de segundos en una hora (60000), teniendo la posibilidad de definir el numero de horas, cuando sea necesario.
08-08: Realizando una operacion con punto flotante.
09-09: Ejemplo de una combinacion de constantes, con variables.

Compiled from "NumberConstantTest.java"
   public class test.NumberConstantTest extends java.lang.Object{
   public test.NumberConstantTest();
     Code:
      0: aload_0
      1: invokespecial #8; //Method java/lang/Object."<init>":()V
      4: return

   public static void main(java.lang.String[]);
     Code:
      0: ldc #16; //int 60000
      2: istore_1
      3: ldc #17; //float 2.35f
      5: fstore_2
      6: iload_1
      7: i2d
      8: ldc2_w #18; //double 0.3333333333333333d
      11: dmul
      12: dstore_3
      13: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream;
      16: iload_1
      17: invokevirtual #26; //Method java/io/PrintStream.println:(I)V
      20: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream;
      23: fload_2
      24: invokevirtual #32; //Method java/io/PrintStream.println:(F)V
      27: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream;
      30: dload_3
      31: invokevirtual #35; //Method java/io/PrintStream.println:(D)V
      34: return
   }

Explicacion
  • En codigo resultante, despues de ejecutar la decompilacion, utilizando el comando "javap -c" se puede apreciar que el compilador calculo los valores y coloco los resultados donde aplica, sin embargo, cuando se trata de combinaciones de constantes, con variables (incluso siendo constantes), no se optimizas y se realiza la operacion.

viernes, 21 de mayo de 2010

operador +=, cadenas, cadenas constante y el compilador en Java

Como se vio en la entrada anterior, el utilizar incorrectamente un operador, puede ser optimizado por el compilador o en su defecto, una instruccion (en apariencia) se puede convertir en un conjunto de instrucciones, que impacta negarivamente en el desempeño del sistema.

El operador += es un ejemplo de operador que nunca se debe utilizar, debido a como esta implementado, veamos este codigo.






1: package test;
2: 
3: public class StringTest {
4:         public static void main(String[] args) {
5:                 String x = "cadena1";
6:                 x += "cadena2";
7:                 System.out.println(x);
8:         }
9: }
10: 

Si de compilamos el codigo java resultante (el archivo .class) con el comando "javap -c", obtenemos lo siguiente:

Compiled from "StringTest.java"
   public class test.StringTest extends java.lang.Object{
   public test.StringTest();
   Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

   public static void main(java.lang.String[]);
   Code:
   0: ldc #16; //String cadena1
   2: astore_1
   3: new #18; //class java/lang/StringBuilder
   6: dup
   7: aload_1
   8: invokestatic #20; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   11: invokespecial #26; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   14: ldc #29; //String cadena2
   16: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   19: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   22: astore_1
   23: getstatic #39; //Field java/lang/System.out:Ljava/io/PrintStream;
   26: aload_1
   27: invokevirtual #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   30: return
   }

El codigo equivalente, para generar el mismo resultado, pero utilizando explicitamente la clase StringBuilder y la funcion valueOf, es el siguiente:

1: package test;
2: 
3: public class StringTest2 {
4:         public static void main(String[] args) {
5:                 String x = "cadena1";
6:                 x = new StringBuilder(String.valueOf(x)).append("cadena2").toString();
7:                 System.out.println(x);
8:         }
9: }
10: 

Pero que ocurre cuando se mete la operacion += dentro de un loop? Como en el siguiente codigo:

01: package test;
02: 
03: public class StringTest3 {
04:         public static void main(String[] args) {
05:                 String x = "cadena1";
06:                 for (int i = 0; i < 10; i++) {
07:                         x += "cadena2";
08:                 }
09:         }
10: }
11: 

Al decompilar, se genera el siguiente codigo:

Compiled from "StringTest3.java"
   public class test.StringTest3 extends java.lang.Object{
   public test.StringTest3();
  Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
  Code:
   0: ldc #16; //String cadena1
   2: astore_1
   3: iconst_0
   4: istore_2
   5: goto 31
   8: new #18; //class java/lang/StringBuilder
   11: dup
   12: aload_1
   13: invokestatic #20; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   16: invokespecial #26; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   19: ldc #29; //String cadena2
   21: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   24: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   27: astore_1
   28: iinc 2, 1
   31: iload_2
   32: bipush 10
   34: if_icmplt 8
   37: return

}

Es decir, el codigo equivalente es el siguiente:

01: package test;
02: 
03: public class StringTest4 {
04:         public static void main(String[] args) {
05:                 String x = "cadena1";
06:                 for (int i = 0; i < 10; i++) {
07:                         x = new StringBuilder(String.valueOf(x)).append("cadena2").toString();
08:                 }
09:         }
10: }
11: 

Como se puede observar, en cada iteracion, se construye un nuevo objeto StringBuilder, con el resultado de ejecutar la funcion valueOf, que recibe como parametro la variable x, posterior mente, se agrega la cadena que "cadena2" y finalmente, se convierte en una cadena nueva, la cual se asigna a la variable x.

Derivado de esto es destacable notar que este codigo es ineficiente, en la mayoria de las situaciones, particularmente cuando se emplea dentro de loops.

Sin embargo, cuando se emplea en conjunto con el operador +, puede eficiente, pero solo si se conoce exactamente la cantidad de elemenos implicados en la operacion. Como en el siguiente ejemplo:

01: package test;
02: 
03: public class StringTest5 {
04:         public static void main(String[] args) {
05:                 String x = "cadena1";
06:                 String y = "auxiliar";
07:                 x += "cadena2" + y;
08:                 System.out.println(x);
09:         }
10: }
11: 

Al de compilar se obtiene lo siguiente:

Compiled from "StringTest5.java"
   public class test.StringTest5 extends java.lang.Object{
   public test.StringTest5();
   Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
   Code:
   0: ldc #16; //String cadena1
   2: astore_1
   3: ldc #18; //String auxiliar
   5: astore_2
   6: new #20; //class java/lang/StringBuilder
   9: dup
   10: aload_1
   11: invokestatic #22; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   14: invokespecial #28; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   17: ldc #31; //String cadena2
   19: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   22: aload_2
   23: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   26: invokevirtual #37; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   29: astore_1
   30: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
   33: aload_1
   34: invokevirtual #47; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   37: return
}

El codigo equivalente, es el siguiente:

01: package test;
02: 
03: public class StringTest6 {
04:         public static void main(String[] args) {
05:                 String x = "cadena1";
06:                 String y = "auxiliar";
07:                 x = new StringBuilder(String.valueOf(x)).append("cadena2").append(y)
08:                                 .toString();
09:                 System.out.println(x);
10:         }
11: }
12: 

Lo cual es eficiente para en terminos de uso de los objetos creados.

martes, 18 de mayo de 2010

operador +, cadenas, cadenas constante y el compilador en Java

"Mucho ayuda el que no estorba"

El uso de cadenas, en un sistema es casi inevitable, pues las personas en general leen y escriben cadenas, independientemente de que estas representen fechas, numeros, palabras, etc.

La generacion de estas cadenas, repercute negativamente en el desempeño de una aplicacion o sistema, sobre todo, cuando se generan y utilizan de manera incorrecta o excesiva.

Un ejemplo es el operador + en el lenguaje de programacion, que cuando es correctamente empleado, puede no ser una carga excesiva, pero cuando se emplea incorrectamete o se mal interpreta su utilizacion, puede provocar que se trabaje de manera excesiva, consumiendo tiempo de procesamiento y memoria indiscriminadamente.

La mayoria de los lenguajes compilados, como Java, particularmente, los que no soportan sobrecarga de operadores por programacion, convierten las instrucciones que tienen el operador + cuando estan operando con cadenas, en objetos y/o funciones. Sin embargo, el compilador, puede tener la capacidad de manipular el codigo resultante, para minimizar el uso de los recursos (memoria y procesador)

En el siguiente codigo, se hace uso del operador +.




01: package test;
02: 
03: public class StringTest {
04:         public static void main(String[] args) {
05:                 String a = "cadena1";
06:                 String b = "cadena2";
07:                 String c = a + b;
08:                 System.out.println(c);
09:         }
10: }
11: 

Posteriormente ejecutamos los siguientes comandos:

   javac test\StringBuilderTest.java

   javap -c -classpath . test.StringTest

Obteniendo el siguiente codigo:

Compiled from "StringTest.java"
   public class test.StringTest extends java.lang.Object{
   public test.StringTest();
   Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

   public static void main(java.lang.String[]);
   Code:
   0: ldc #16; //String cadena1
   2: astore_1
   3: ldc #18; //String cadena2
   5: astore_2
   6: new #20; //class java/lang/StringBuilder
   9: dup
   10: aload_1
   11: invokestatic #22; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   14: invokespecial #28; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   17: aload_2
   18: invokevirtual #31; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   21: invokevirtual #35; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   24: astore_3
   25: getstatic #39; //Field java/lang/System.out:Ljava/io/PrintStream;
   28: aload_3
   29: invokevirtual #45; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   32: return
   }

Como se puede observar, el compilador, tradujo la sentencia con el operador + en el uso de la funcion valueOf, la creacion de un objeto StringBuilder, el uso de la funcion append y finalmente toString, es decir algo como esto:



01: package test;
02: 
03: public class StringTest2 {
04:         public static void main(String[] args) {
05:                 String a = "cadena1";
06:                 String b = "cadena2";
07:                 String c = new StringBuilder(String.valueOf(a)).append(b).toString();
08:                 System.out.println(c);
09:         }
10: }
11: 

Nosotros conocemos que se estan empleando constantes, por lo que podemos substituir la variable por el valor.


1: package test;
2: 
3: public class StringTest3 {
4:         public static void main(String[] args) {
5:                 String c = "cadena1" + "cadena2";
6:                 System.out.println(c);
7:         }
8: }
9: 

Ejecutamos la compilacion (javac) y de compilacion (javap), obteniendo lo siguiente:

Compiled from "StringTest3.java"
   public class test.StringTest3 extends java.lang.Object{
   public test.StringTest3();
   Code:
   0: aload_0
   1: invokespecial #8; //Method java/lang/Object."<init>":()V
   4: return

   public static void main(java.lang.String[]);
   Code:
   0: ldc #16; //String cadena1cadena2
   2: astore_1
   3: getstatic #18; //Field java/lang/System.out:Ljava/io/PrintStream;
   6: aload_1
   7: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   10: return
   }

Este es el codigo equivalente, el cual genera el mismo codigo compilado, que el codigo anterior.



1: package test;
2: 
3: public class StringTest4 {
4:         public static void main(String[] args) {
5:                 String c = "cadena1cadena2";
6:                 System.out.println(c);
7:         }
8: }
9: 

Como se puede notar, cuando se utilizan constantes, el operador + y el compilador, pueden optimizar el codigo, siempre y cuando la constante este "inline". De ahi, la frase inicial "mucho ayuda el que no estorba", en ocasiones hay que dejar que el compilador haga su trabajo.