C++/CLI y C#: Asombrosas diferencias en el optimizador de código (y VI)

Tras la petición de Vicente en la entrada anterior, me he decidido a realizar unas pruebas de rendimiento sintético, utilizando el perfilador de código del propio Visual Studio 2005.


En su momento hice una serie de pruebas con el programa original, es decir, el que imprime una línea de texto por pantalla en la llamada a CallPrint(), pero descubrí una variación de tiempos asombrosa en diferentes ejecuciones del mismo programa. Tras mirar el código generado en la llamada, y pensar seriamente, llegué a la conclusión de que realizar un perfilado con una llamada a Console::WriteLine() no era una algo válido, ya que dicha llamada depende de un montón de cosas que no se pueden controlar, como rodajas de acceso a la memoria de vídeo, el propio sistema actualizando la pantalla y demás. Así que para las pruebas de rendimiento he decidido cambiar la línea citada por otra más sintética: una suma. Veamos el código de ambos proyectos (A la izquierda el código en C++/CLI y a la derecha en C#):











#include «stdafx.h»


using namespace System;


ref class Program
{
    static Int32 i=0;
public: 
    static void CallPrint(void) 
    { 
        i+=1; 
    } 
    static void DoWork(void) 
    { 
        for(int i=0;i<1000;i++) 
            CallPrint(); 
    }
};


int main(array<System::String ^> ^args)

    Program::DoWork();     
    Console::WriteLine(L»Hello World»); 
    return 0;


}


using System;
using System.Collections.Generic;
using System.Text;


namespace TestCS1
{
  class Program 
  { 
    static Int32 i=0;
    static void CallPrint()
    {
     i += 1;
    }
    static void DoWork() 
    { 
      for (int i = 0; i < 1000; i++)
      CallPrint();
    }
    static void Main(string[] args) 
    {
      DoWork();
      Console.WriteLine(«Hello World»);
    }
  }
}


Ahora, nos vamos al menú Tools -> Performance Tools -> Performance Wizard y creamos un perfilado para ambos proyectos, del tipo Instrumentation. Y los ejecutamos, por supuesto ambos en Release.


Tras el análisis, podemos observar que en el programa C++/CLI el método CallPrint() no existe y ha sido integrado con el DoWork(), tal y como era de esperar. Veamos primero las imágenes combinadas:



Los resultados son lo esperado. La llamada a DoWork() en C++/CLI ha tardado 0.003489 milisegundos a realizarse, mientras que la suma de DoWork() y las 1000 a CallPrint() ha sido de 0.319240 milisegundos. Un cálculo rápido nos dice que la ejecución del bloque ha sido unas 91.5 veces más rápida. O en otras palabras: mientras que el programa en C# hace un bucle de 1000, el de C++/CLI podría hacerlo de 91 500.


Ahora bien, lo que no se ve en la imagen es el tiempo total de ejecución. El programa en C++/CLI ha tardado algo menos de 60 ms, mientras que el de C# apenas ha sido de 1,5 ms. Igual que antes, la diferencia es asombrosamente dispar, pero aquí sí que tiene una justificación lógica: un programa en C++/CLI necesita cargar, inicializar y sincronizar dos entornos de ejecución. Por un lado ha de cargar toda la parafernalia del .NET, y por otro llamar al startup nativo. Luego ha de mezclar ambos entornos mediante varias sincronizaciones y llamadas a métodos para unir los dos contextos. Pero luego, como se puede ver, no hay color en cuanto al rendimiento.

2 comentarios sobre “C++/CLI y C#: Asombrosas diferencias en el optimizador de código (y VI)”

  1. Este resultado si que es mucho más interesante 🙂 A ver si un día me pongo y hago un profiling serio de lo que nos dió pasar de C# a C++/CLI en ciertas partes de Jade, aunque es un 10% más o menos lo que ganamos.

    Pero vamos, esos números si deberían preocupar más a la gente del Jitter. Un saludo!

    Vicente

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *