Compartir a través de


Latencia y rendimiento de red

Tres problemas principales se relacionan con el uso óptimo de la red:

  • Latencia de red
  • Saturación de red
  • Implicaciones de procesamiento de paquetes

En esta sección se presenta una tarea de programación que requiere el uso de RPC y, a continuación, se diseñan dos soluciones: una mal escrita y otra bien escrita. A continuación, se examinan ambas soluciones y se analiza su efecto en el rendimiento de la red.

Antes de analizar las dos soluciones, en las secciones siguientes se describen y se aclaran los problemas de rendimiento relacionados con la red.

Latencia de red

El ancho de banda de red y la latencia de red son términos independientes. Las redes con ancho de banda alto no garantizan una latencia baja. Por ejemplo, una ruta de acceso de red que atraviesa un vínculo satélite suele tener una latencia alta, aunque el rendimiento es muy alto. No es raro que un recorrido de ida y vuelta de red que atraviesa un vínculo satélite tenga cinco o más segundos de latencia. La implicación de este retraso es esto: una aplicación diseñada para enviar una solicitud, esperar una respuesta, enviar otra solicitud, esperar otra respuesta, etc., esperará al menos cinco segundos para cada intercambio de paquetes, independientemente de la rapidez del servidor. A pesar de la velocidad creciente de los ordenadores, las transmisiones por satélite y los medios de red se basan en la velocidad de la luz, que generalmente permanece constante. Por lo tanto, es poco probable que se produzcan mejoras en la latencia de las redes satélite existentes.

Saturación de red

Algunas saturaciones se producen en muchas redes. Las redes más fáciles de saturar son enlaces de módem lento, como módems analógicos estándar de 56k. Sin embargo, los vínculos Ethernet con muchos equipos de un solo segmento también pueden saturarse. Lo mismo sucede con las redes de área extensa con un ancho de banda bajo o un vínculo sobrecargado, como un enrutador o conmutador que puede controlar una cantidad limitada de tráfico. En estos casos, si la red envía más paquetes que su vínculo más débil puede controlar, quita los paquetes. Para evitar la congestión de la pila TCP de Windows se reduce verticalmente cuando se detectan paquetes descartados, lo que puede dar lugar a retrasos significativos.

Implicaciones de procesamiento de paquetes

Cuando los programas se desarrollan para entornos de nivel superior, como RPC, COM e incluso Windows Sockets, los desarrolladores tienden a olvidar cuánto trabajo tiene lugar en segundo plano para cada paquete enviado o recibido. Cuando llega un paquete desde la red, el equipo atenderá una interrupción de la tarjeta de red. A continuación, se pone en cola una llamada a procedimiento diferido (DPC) y debe recorrer los controladores. Si se usa cualquier forma de seguridad, es posible que el paquete tenga que descifrarse o comprobar el hash criptográfico. También se deben realizar varias comprobaciones de validez en cada estado. Solo entonces el paquete llega al destino final: el código del servidor. El envío de muchos fragmentos pequeños de datos da como resultado una sobrecarga de procesamiento de paquetes para cada pequeño fragmento de datos. El envío de un gran fragmento de datos tiende a consumir mucho menos tiempo de CPU en todo el sistema, aunque el costo de ejecución de muchos fragmentos pequeños en comparación con un fragmento grande puede ser el mismo para la aplicación de servidor.

Ejemplo 1: un servidor RPC mal diseñado

Imagine una aplicación que debe tener acceso a archivos remotos y la tarea a mano es diseñar una interfaz RPC para manipular el archivo remoto. La solución más sencilla es reflejar las rutinas de archivo de Studio para los archivos locales. Si lo hace, puede dar lugar a una interfaz poco limpia y familiar. Este es un archivo .idl abreviado:

typedef [context_handle] void *remote_file;
... .
interface remote_file
{
    remote_file remote_fopen(file_name);
    void remote_fclose(remote_file ...);
    size_t remote_fread(void *, size_t, size_t, remote_file ...);
    size_t remote_fwrite(const void *, size_t, size_t, remote_file ...);
    size_t remote_fseek(remote_file ..., long, int);
}

Esto parece lo suficientemente elegante, pero en realidad, esta es una receta que respeta el tiempo para el desastre de rendimiento. Contrariamente a la opinión popular, la llamada a procedimiento remoto no es simplemente una llamada a procedimiento local con una conexión entre el autor de la llamada y la llamada.

Para ver cómo esta receta quema el rendimiento, considere un archivo 2K, donde se leen 20 bytes desde el principio y, a continuación, 20 bytes desde el final, y vea cómo funciona esto. En el lado cliente, se realizan las siguientes llamadas (se omiten muchas rutas de acceso de código para mayor brevedad):

rfp = remote_fopen("c:\\sample.txt");
remote_read(...);
remote_fseek(...);
remote_read(...);
remote_fclose(rfp);

Ahora imagine que el servidor está separado del cliente por un vínculo satélite con un tiempo de ida y vuelta de cinco segundos. Cada una de esas llamadas debe esperar una respuesta para poder continuar, lo que significa un mínimo absoluto para ejecutar esta secuencia de 25 segundos. Teniendo en cuenta que estamos recuperando solo 40 bytes, este es un rendimiento indignantemente lento. Los clientes de esta aplicación estarían furiosos.

Ahora imagine que la red está saturada, porque la capacidad de un enrutador en algún lugar de la ruta de acceso de red se sobrecarga. Este diseño obliga al enrutador a controlar al menos 10 paquetes si no tenemos seguridad (uno para cada solicitud y otro para cada respuesta). Eso tampoco es bueno.

Este diseño también obliga al servidor a recibir cinco paquetes y enviar cinco paquetes. De nuevo, no es una implementación muy buena.

Ejemplo 2: un servidor RPC mejor diseñado

Vamos a rediseñar la interfaz que se describe en el ejemplo 1 y ver si podemos mejorarla. Es importante tener en cuenta que hacer que este servidor sea realmente bueno requiere conocimiento del patrón de uso de los archivos especificados: este conocimiento no se supone para este ejemplo. Por lo tanto, se trata de un servidor RPC mejor diseñado, pero no un servidor RPC diseñado de forma óptima.

La idea de este ejemplo es contraer tantas operaciones remotas como sea posible. El primer intento es el siguiente:

typedef [context_handle] void *remote_file;
typedef struct
{
    long position;
    int origin;
} remote_seek_instruction;
... .
interface remote_file
{
    remote_fread(file_name, void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
    size_t remote_fwrite(file_name, const void *, size_t, size_t, [in, out] remote_file ..., BOOL CloseWhenDone, remote_seek_instruction *...);
}

En este ejemplo se contraen todas las operaciones en una lectura y escritura, lo que permite una apertura opcional en la misma operación, así como un cierre y una búsqueda opcionales.

Esta misma secuencia de operación, cuando se escribe en forma abreviada, tiene este aspecto:

remote_read("c:\\sample.txt", ..., &rfp, FALSE, NULL);
remote_read(NULL, ..., &rfp, TRUE, seek_to_20_bytes_before_end);

Al considerar el servidor RPC mejor diseñado, en la segunda llamada al servidor comprueba que el file_name es NULLy usa el archivo abierto almacenado en rfp. A continuación, ve que hay instrucciones de búsqueda y colocará el puntero de archivo 20 bytes antes de que se lea. Cuando haya terminado, reconocerá la marca CloseWhenDone está establecida en TRUEy cerrará el archivo y cerrará rfp.

En la red de alta latencia, esta versión mejor tarda 10 segundos en completarse (2,5 veces más rápido) y requiere el procesamiento de solo cuatro paquetes; dos recibe del servidor y dos envía desde el servidor. La adicional si y la desenshacimiento que realiza el servidor son insignificantes en comparación con todo lo demás.

Si la ordenación causal se especifica correctamente, la interfaz se puede realizar incluso asincrónica y las dos llamadas se pueden enviar en paralelo. Cuando se usan las llamadas de ordenación causal se siguen enviando en orden, lo que significa que en la red de alta latencia solo se soporta un retraso de cinco segundos, aunque el número de paquetes enviados y recibidos sea el mismo.

Podemos contraer esto aún más creando un método que toma una matriz de estructuras, cada miembro de la matriz que describe una operación de archivo determinada; una variación remota de dispersión/recopilación de E/S. El enfoque paga siempre que el resultado de cada operación no requiera procesamiento adicional en el cliente; es decir, la aplicación va a leer los 20 bytes al final, independientemente de cuáles son los primeros 20 bytes leídos.

Sin embargo, si se debe realizar algún procesamiento en los primeros 20 bytes después de leerlos para determinar la siguiente operación, contraer todo en una operación no funciona (al menos no en todos los casos). La elegancia de RPC es que una aplicación puede tener ambos métodos en la interfaz y llamar a cualquiera de los métodos según sea necesario.

En general, cuando la red está implicada, es mejor combinar tantas llamadas en una sola llamada como sea posible. Si una aplicación tiene dos actividades independientes, use operaciones asincrónicas y deje que se ejecuten en paralelo. Básicamente, mantenga la canalización llena.