Mejorar el Rendimiento por Diseño

Las restricciones del ancho de banda en las redes alrededor del mundo hacen de las operaciones basadas en red potenciales cuellos de botella que pueden tener un importante impacto en el rendimiento de las aplicaciones. Muchas aplicaciones de red están disañadas para usar almacenes de conexiones y por ello pueden reutilizar conexiones de red existentes y ahorrar el tiempo y la sobrecarga que conllevan el abrir y cerrar conexiones de red.

Junto con el almacen de conexiones, hay otras características que podemos diseñar dentro de nuestros programas para mejorar el rendimiento. Este capítulo explica cómo podemos diseñar un applet para que descargue ficheros y recursos de forma más eficiente, o diseñar un programa basado en threads para usar un almacen de threads para ahorrarnos el costoso proceso de arrancar threads.

Mejorar la Velocidad de Descarga de un Applet

El rendimiento de descarga de un applet se refiere al tiempo que tarda el navegador en descargar todos los ficheros y recursos que necesita para arrancar el applet. Un factor importante que afecta al rendimiento de la descarga del applet es el número de veces que tiene que solicitar datos al servidor. Podemos reducir el número de peticiones empaquetando las imagenes del applet en un fichero class, o usando un archivo JAR.

Empaquetar Imágenes en un Clase

Normalmente, si un applet tiene seis imágenes de botones se traducen en seis solicitudes adicionales al servidor para cargar esos ficheros de imágenes. Seis solicitudes adicionales podrían no parecer demasiadas en una red interna, pero en las conexiones de baja velocidad y eficiencia, esas solicitudes adicionales pueden tener un impacto muy negativo en el rendimiento. Por eso, nuestro último objetivo será cargar el applet tan rápido como sea posible.

Una forma de almacenar imágenes en un fichero class es usar un esquema de codificación ASCII como X-PixMap (XPM). De esta forma, en vez de mantener la imágenes en ficheros GIF en el servidor, los ficheros son codificados como un Strings y son almacenados en un sólo fichero class.

Este código de ejemplo usa páquetes del ganador de la JavaCup del JavaOne 1996, que contenía las clases XImageSource y XpmParser. Estas clases proporciona todo los necesario para leer un fichero XPM. Podemos ver esto ficheros en SunSite.

Para el proceso inicial de codificación, hay un número de herramientas gráficas que podemos usar para crear fichero XPM. En Solaris podemos usar ImageTool o una variedad de otros GNU image packages. Podemos ir a la web site Download.com para obtener software de codificación para las plataformas Windows.

El siguiente código extraido del ejemplo de código MyApplet que carga imágenes. Podemos ver el String codificado en la definición XPM de imágenes.

La clase Toolkit crea un objeto Image para cada imagen desde el objeto fuente XPM Image.

Toolkit kit = Toolkit.getDefaultToolkit();
  Image image;
  image = kit.createImage (new XImageSource (_reply));
  image = kit.createImage (new XImageSource (_post));
  image = kit.createImage (new XImageSource (_reload));
  image = kit.createImage (new XImageSource (_catchup));
  image = kit.createImage (new XImageSource (_back10));
  image = kit.createImage (new XImageSource (_reset));
  image = kit.createImage (new XImageSource (_faq));
La alternativa técnica de abajo usa ficheros GIF. Requiere una petición al servidor para cada imagen cargada.
Image image;
  image = getImage ("reply.gif");
  image = getImage ("post.gif");
  image = getImage ("reload.gif");
  image = getImage ("catchup.gif");
  image = getImage ("back10.gif");
  image = getImage ("reset.gif");
  image = getImage ("faq.gif");
Esta técnica reduce el trafico de la red porque todas las imágenes están disponibles en un sólo fichero class.

Una vez cargado, podemos usar las imágenes para crear botones u otros componentes del interface de usuario. El siguiente segmento de código muestra cómo usar la imágenes con la clase javax.swing.JButton.

ImageIcon icon = new ImageIcon (
                   kit.createImage (
		     new XImageSource (_reply))); 
JButton button = new JButton (icon, "Reply");

Usar Ficheros JAR

Cuando un applet consta de más de un fichero, podemos mejorar el rendimiento de la descarga con ficheros JAR. Un fichero JAR contiene todos los ficheros del applet en un sólo fichero más rápido de dsacargar. Mucha parte del tiempo ahorrado viene de la reducción del número de conexiones HTTP que el navegador tiene que hacer.

El capítulo: Desarrollar Nuestra Aplicación tiene información sobre cómo crear y firmar ficheros JAR.

El código HTML de abajo usa la etiqueta CODE para especificar el ejecutable del applet MyApplet, y la etiqueta ARCHIVE especifica el fichero JAR que contiene todos los ficheros relacionados con MyApplet. El ejecutable especificado por la etiqueta CODE algunas veces es llamado code base.

Por razones de seguridas los ficheros JAR listados por el parámetro archive deben estar en el mismo directorio o subdirectorio que el codebase del applet. Si no se suministra el parámetro codebase el directorio de donde se cargó el applet se usa como el codebase.

El siguiente ejemplo especifica jarfile como el fichero JAR que contiene todos los ficheros relacionados para el ejecutable MyApplet.class.

<APPLET CODE="MyApplet.class" ARCHIVE="jarfile" WIDTH="100" HEIGHT="200"> </APPLET>

Si la descarga del applet usa múltiples ficheros JAR como se muestra en el siguiente segmento HTML, el ClassLoader carga cada fichero JAR cuando el applet arranca. Por eso, si nuestro applet usa algunos ficheros de recursos de forma infrecuente, el fichero JAR que contiene esos ficheros es descargado sin importar si los recursos van a ser usados durante la sesión o no.

<APPLET CODE="MyApplet.class" ARCHIVE="jarfile1, jarfile2" WIDTH="100" HEIGHT="200"> </APPLET>

Para mejorar el rendimiento cuando se descargan fichero no usados de forma frecuente, ponemos los ficheros usados más frecuentemente dentro de un fichero JAR y los ficheros menos usados en el directorio de la clase del applet. Los ficheros usados poco frecuentemente son localizados y descargados sólo cuando el navegador los necesita.

Almacen de Threads

El servidor de applets Java Developer ConnectionSM (JDC) y el Java Web ServerTM hacen un uso extensivo del almacen de threads para mejorar el rendimiento. El almacen de threads es crear un suministro de threads durmientes al principio de la ejecución. Como el proceso de arranque de un thread es muy caro en términos de recursos del sistema, el almacen de threads hace el proceso de arrancada un poco más lento, pero aumenta el rendimiento en tiempo de ejecución porque los threads durmientes (o suspendidos) sólo se despiertan cuando cuando son necesarios para realizar nuevas tareas.

Este código de ejemplo tomado de la clase Pool.java muestra una forma de implementar la fusión de threads, En el constructor de la fusión (mostrado abajo), se inicializan y arrancan los WorkerThreads. La llamada al método start ejecuta el método run del WorkerThread, y la llamada a wait suspende el Thread mientras el Thread espera a que llegue un trabajo. La última línea del constructor empuja el Thread durmiente hacia la pila.

public Pool (int max, Class workerClass) 
                         throws Exception {

    _max = max;
    _waiting = new Stack();
    _workerClass = workerClass;
    Worker worker;
    WorkerThread w;
    for ( int i = 0; i < _max; i++ ) {
        worker = (Worker)_workerClass.newInstance();
        w = new WorkerThread ("Worker#"+i, worker);
        w.start();
        _waiting.push (w);
    }
}
Junto al método run, la clase WorkerThread tiene un método wake. Cuando viene el trabajo, se llama al método wake, que asigna los datos y notifica al WorkerThread durmiente (el inicializado por el Pool) para recuperar la ejecución. El método wake llama a notify hace que el WorkerThread bloqueado salga del estado de espera, y se ejecuta el método run de la clase HttpServerWorker. Una vez realizado el trabajo, el WorkerThread se pone de nuevo en el Stack (asumiento que el Pool de threads no está lleno) o termina.
synchronized void wake (Object data) {
  	_data = data; 
	notify();
  }

  synchronized public void run(){
    boolean stop = false; 
    while (!stop){ 
	if ( _data == null ){ 
	  try{
	     wait();
	  }catch (InterruptedException e){ 
		e.printStackTrace();
		continue;
	  }
	}

	if ( _data != null ){
	  _worker.run(_data);
	}

	_data = null;
	stop = !(_push (this));
    }
  }
En este alto nivel, el trabajo entrante es manejado por el método performWork en la clase Pool. Cuando viene el trabajo, se saca de la pila un WorkerThread existente (o se crea uno nuevo si el Pool está vacío). El WorkerThread durmiente es activado mendiate una llamada a su método wake.
public void performWork (Object data) 
               throws InstantiationException{
  WorkerThread w = null;
  synchronized (_waiting){
     if ( _waiting.empty() ){
	try{
	  w = new WorkerThread ("additional worker",
	  (Worker)_workerClass.newInstance());
	  w.start();
	}catch (Exception e){
	  throw new InstantiationException (
	              "Problem creating 
	              instance of Worker.class: " 
	              + e.getMessage());
	}
      }else{
	w = (WorkerThread)_waiting.pop();
      }
  }
  w.wake (data);
}
El constructor de la clase HttpServer.java crea un nuevo ejemplar Pool para servir ejemplares de la clase HttpServerWorker. Los ejemplares HttpServerWorker se crean y almacenan como parte de los datos WorkerThread. Cuando se activa un WorkerThread mediante una llamada a su método wake, el ejemplar HttpServerWorker es invocado mediante su método run.
try{
    _pool = new Pool (poolSize, 
                  HttpServerWorker.class);
  }catch (Exception e){
    e.printStackTrace();
    throw new InternalError (e.getMessage());
  }
Este código está en el método run de la clase HttpServer.java. Cada vec que viene una petición, el dato es inicializado y el Thread empieza el trabajo.

Nota: Si creamos un nuevo Hashtable por cada WorkerThread provocamos demasiada sobrecarga, sólo modificamos el código para que no use la abstración Worker.
try{
	Socket s = _serverSocket.accept();
	Hashtable data = new Hashtable();
	data.put ("Socket", s); 
	data.put ("HttpServer", this);
	_pool.performWork (data); 
  }catch (Exception e){ 
	e.printStackTrace();
  }
El almacen de threads es una técnica efectiva de ajuste de rendimiento que coloca el caro proceso de arranque de threads en la arrancada de la aplicación. De esta forma, el impacto negativo en el rendimiento ocurre sólo una vez durante el arrancada del programa donde se nota menos.

Ozito