2. Higiene

Asumiendo que nuestro sistema puede mejorar su performance (en la mayoría ocurre así), lo primero es corregir ciertos problemas típicos. En particular:

Llamaremos "pesado" a un proceso que torna lento al sistema. Esto ocurre comunmente porque aquél consume muchos recursos de procesamiento (mucho CPU) o consume mucha memoria.

2.1. Consumo excesivo de CPU de un proceso

Muchas veces un proceso que está fuera de control (por un error en su programación) entra en loops ilimitados que consumen inutilmente el CPU; en otros casos, esto ocurre de manera "normal" durante la ejecución de un proceso frecuente[1]. Más allá de analizar el por qué ocurre esto (que es más responsabilidad del programador), preocupémonos por detectarlo. Para esto, la manera más sencilla quizá sea emplear el comando:

# ps axu
(Consúltese el manual de ps para una explicación de sus opciones.) En particular, estamos interesados en la columna que reza "%CPU". Esta columna proporciona el porcentaje que representa el tiempo de CPU consumido por el proceso respecto al tiempo total de su ejecución.

El siguiente programa se ejecuta durante 20 segundos. Los 10 primeros el proceso no consume CPU, y luego inicia un loop sin descanso hasta que transcurran otros 10 segundos. Más abajo se muestra el monitoreo del mismo en diversos momentos para un sistema sin carga:

#include <stdio.h>
#include <unistd.h>
#include <time.h>

#define DELAY 10

main()
{
volatile int z;
time_t t;

/* dormir DELAY segundos */
sleep(DELAY);

/* loop infinito durante DELAY segundos */
t=time(NULL);
for(z=0;;z++)
	if(time(NULL)>=t+DELAY)
		break;
return 0;
}
Ejemplo de uso[2]:
$ ./sleep_and_run &
[1] 1194
$ ps axu|head -1
USER   PID  %CPU %MEM   VSZ  RSS STAT START  TIME COMMAND
$ ps axu|grep sleep_and_ru[n]
diego  1141  0.0  0.0  1376  252 S    22:01  0:00 ./sleep_and_run
$ ps axu|grep sleep_and_ru[n]
diego  1141  0.0  0.0  1376  252 S    22:01  0:00 ./sleep_and_run
$ ps axu|grep sleep_and_ru[n]
diego  1141 31.6  0.0  1376  256 R    22:01  0:02 ./sleep_and_run
$ ps axu|grep sleep_and_ru[n]
diego  1141 49.8  0.0  1376  256 R    22:02  0:05 ./sleep_and_run
$ ps axu|grep sleep_and_ru[n]
diego  1141 51.2  0.0  1376  256 R    22:01  0:06 ./sleep_and_run
$ ps axu|grep sleep_and_ru[n]
$
Como se aprecia, la columna %CPU empieza a crecer tras unos momentos hasta alcanzar un valor significativo (51.2%.)

Lamentablemente, este procedimiento no detecta los procesos que de pronto salen de control si éstos ya se han venido ejecutando durante mucho tiempo manteniendo un consumo moderado de CPU (pues en ese caso el %CPU tardará mucho en hacerse significativo.)[3]

Afortunadamente, existe otra forma muy sencilla de analizar este caso, y consiste en verificar la columna TIME del proceso en diversos momentos. Si se aprecia el ejemplo anterior, se observa que la columna TIME se mantiene estable (en este caso, con 0:00) durante los primeros momentos, y luego rápidamente se incrementa. Esto es un indicativo claro de que el proceso está consumiendo mucho de nuestro CPU.

En este punto, el administrador deberá matar inmediatamente al proceso, si éste resulta ser inútil. En cambio, si el proceso cumple alguna función de utilidad en el sistema, se deberá esperar un tiempo prudencial para que vuelva a la normalidad (deje de consumir tanto CPU) o para que termine.

2.2. Consumo excesivo de Memoria de un proceso

A veces asociado con lo anterior, los procesos pueden consumir demasiada memoria por distintos motivos:

Una manera rápida de saber si el sistema en su conjunto tiene carencia de memoria física consiste en analizar las columnas 'si' y 'so' bajo '--swap--'[4]:

$ vmstat 2
procs -----------memory---------- ---swap-- -----io----
 r  b   swpd   free   buff  cache   si   so    bi    bo
 0  0      0 321408  11960  95300    0    0    36     9
 0  0      0 321424  11960  95300    0    0     0     4
 0  0      0 321424  11960  95300    0    0     0     0
Si los valores en las columnas mencionadas son cero (quizá con pequeñas excepciones esporádicas), quiere decir que el sistema tiene suficiente memoria física para los requerimientos DE ESTE MOMENTO. Por lo tanto, el administrador hará bien en analizar la salidad de vmstat en diferentes momentos del día, o cuando se ejecutan los procesos cuya lentitud nos preocupa más.

Con el fin de analizar la memoria consumida por los procesos, podemos referirnos a las columnas %MEM y RSS de la salida nuestro conocido 'ps axu', que son equivalentes. RSS corresponde a la memoria que el sistema operativo ha otorgado para el proceso, el cual se presenta como porcentaje respecto al total en %MEM.

Siguiendo la idea, es una buena idea analizar qué procesos consumen mucha memoria y desactivarlos si no son imprescindibles. Más abajo se explica con detalle qué se puede hacer si tenemos un sistema con carencia de memoria en el que no podemos desactivar los procesos culpables.

2.3. Re-escritura de procesos pesados

No pretendo entrar al tópico de optimizar la ejecución de los programas. En mi opinión, los buenos programadores casi siempre pueden optimizar la ejecución de sus programas para que consuman menos CPU o Memoria, aunque no es una tarea trivial que conlleve poco tiempo. El problema aquí no es técnico, sino de "gerencia": el programador suele ser obligado a liberar una versión operativa que rápidamente pasa a producción, y luego se le reasigna a otra actividad sin darle tiempo para (ni obligarle a) que la optimice. Los jefes de IT se sorprenderían gratamente si asignaran a sus buenos programadores exclusivamente a optimizar las aplicaciones por algunos días.

Un aspecto imprescindible de resaltar porque resulta evidente, es el consumo excesivo de recursos que hacen los programas desarrollados en lenguajes tipo scripting como el Shell. Si bien es cierto que programar en el Shell (o similares) tiene múltiples ventajas, se debe tener en cuenta que una aplicación equivalente reescrita en lenguaje C suele ser de 10 a mil veces más veloz, y por tanto mucho menos derrochadora de CPU y Memoria.

El shell tiene su lugar para diversas tareas administrativas y para prototipado de cierta clase de aplicaciones, pero rara vez debería constituirse en el lenguaje de una aplicación principal. Como se sabe, muchas veces ocurre que la falta de tiempo o la desidia hace que la gente utilice el prototipo y postergue (para siempre) el desarrollo "en serio".

Notas

[1]

Es aceptable que un proceso consuma mucho CPU por pocos segundos o minutos. Son muy escasos los procesos que consumen mucho CPU durante tiempos extendidos.

[2]

Para compilar el programa, usar algo como: cc -o sleep_and_run sleep_and_run.c

[3]

En algunos sistemas Unix, el %CPU se toma en relación a los últimos segundos de ejecución, por lo que no se presenta este inconveniente. Por otro lado, en sistemas multiprocesador el %CPU suele alcanzar 100/N% por proceso o por thread, donde N es el número de procesadores.

[4]

En algunos sistemas Unix esto se separa en columnas de "paginación" tales como 'page-in' y 'page-out'.