Linux Overcommit (III): Memoria libre, caché y swappiness

Continuamos la saga del overcommit. En el anterior artículo expliqué qué es el memory overcommit, pero tengo la sensación de que la confusión viene de no saber realmente cómo funciona la memoria en un sistema operativo moderno.

Antes de entrar al trapo con la memoria virtual y el aislamiento y los page faults, quiero dar un breve repaso a conceptos más mundanos: ¿Porqué existe la swap? ¿Cual es el cometido de la memoria caché?

Así que sin más preámbulo, empecemos.

Memoria libre y caché

Cuando se intenta leer un fichero lo lógico es leer el fichero del disco. Pero esto es muy lento comparado con la memoria RAM. Un disco duro tradicional puede leer 20MB/s secuencialmente pero apenas unos míseros KB/s si es lectura aleatoria. Leer cientos de ficheros pequeños del disco es una pesadilla para el disco duro.

Por eso, si hay memoria RAM que no está siendo usada por programas, el sistema operativo (también en Windows) copia lo que se lee a memoria. Cuando un fichero se quiere leer por segunda vez si está en la memoria caché y sabemos que no ha cambiado, no hace falta leer de disco. Esto acelera el sistema una barbaridad, porque la memoria RAM puede leer más de 4GB/s en secuencial, lo que sería 200 veces más rápido que del disco. Pero más importante, el acceso aleatorio en RAM sigue siendo de varios GB/s con un tiempo de espera menor de un milisegundo.

Tener todos los ficheros de acceso frecuente en memoria caché es crucial para que el sistema vaya fluido. Otra cosa dónde los discos duros lo llevan fatal es cuando varios programas solicitan datos a la vez; el disco tarda en mover la aguja y los tiempos de espera se acumulan. Se forman colas que se van alargando y lo que normalmente era un tiempo de espera de unas centésimas de segundo se convierte en segundos. Al rato si la situación continúa las colas pueden ser de minutos. El sistema se queda frito.

Aquí podemos ver a los programas haciendo cola para acceder al disco

Para evitar esto es crucial reducir las operaciones de I/O al mínimo necesario. Al tener ciertos ficheros (o partes) en memoria, podemos descargar al disco duro de todas estas operaciones. Otras lecturas en cambio no se pueden cachear porque son demasiado heterogéneas, hay programas que simplemente cada vez leen algo totalmente distinto y no se pueden beneficiar de la caché. En otros casos simplemente son peticiones distintas a las vistas hasta el momento, por lo que se tienen que leer de disco sí o sí. Al haber descargado el disco duro de todas las tareas repetitivas, cuando llegan las otras no van a ver casi cola para acceder al disco, por lo que los tiempos de acceso van a ser óptimos.

La memoria caché que viene de los ficheros de disco es volátil y un programa al intentar reclamar más memoria no va a notar ningún problema de rendimiento porque el sistema operativo simplemente va a liberar inmediatamente las memoria cacheada necesaria para darle espacio al programa.

Por otra parte, no toda la memoria caché es estrictamente caché de ficheros. Los mmap por ejemplo cuentan como memoria caché (concretamente buffers), pero Linux puede ser más reticente a liberarla cuando cree que los procesos están activamente usándola.

Nota: El sistema operativo lee casi siempre más de la cuenta al leer ficheros; básicamente se entiende que cuando se lee una parte, lo normal es continuar leyendo, por lo que hay un parámetro llamado readahead que controla cuántos bytes se leen de más. Éstos van a la caché a la espera de que una aplicación los necesite. Por defecto es de 128KB.

Sobre el Swappiness

Cuando el sistema tiene muchos programas funcionando pero no están activamente usando toda la memoria, Linux y otros sistemas operativos pueden decidir en ciertos casos que la memoria caché es más importante que la memoria inactiva.

Esto pasa especialmente cuando el sistema tiene mucho uso de disco de forma continuada y lee cantidades muy grandes de datos de forma constante. Cachear estos datos en memoria haría que la cantidad de I/O del disco se reduzca mucho, pero hay demasiados programas en funcionamiento.

En estos casos Linux decide que lo mejor es pasar la memoria poco usada a swap para dejar más memoria libre para la caché. Otros casos donde ocurre lo mismo son por ejemplo cuando los mmap están trabajando con regiones de ficheros muy grandes de forma constante y accediendo de forma aleatoria. O cuando hay algún programa que está solicitando memoria y no hay suficiente memoria libre en RAM.

La obsesión de Linux para enviar memoria de procesos a swap se le llama “swappiness” y es un parámetro configurable que va de 0 a 100. Por defecto está a 60 y puede ser un poco alto para según qué usos.

En términos sencillos, cuando swappiness vale 0, Linux no moverá nada a swap a no ser que sea absolutamente necesario; es decir, que se está quedando sin memoria RAM para los procesos del sistema. Cuando vale 100, Linux intentará mover la mayor cantidad de memoria posible a swap cuando vea que tener más caché sería beneficioso.

Desde mi experiencia, los equipos de usuario que corren diversos programas pero pocos están trabajando a la vez, el valor por defecto de 60 es óptimo. Ésto hace que Linux se adelante y empiece a mover a swap lo que no se usa, haciendo que cuando el usuario abra otro programa más ya esté el movimiento a swap hecho de antemano y no haya demasiados problemas de rendimiento.

Por otro lado, aquellos servidores que trabajan con un set de programas fijos, dónde todos son críticos y todos tienen que estar disponibles para responder, tener un swappiness tan alto provoca que programas como SSH se muevan a swap y luego tarden en responder al intentar conectarse. Por ello yo suelo configurar el swappiness en estos casos entre 10 y 30, según el uso exacto.

Hay que tener en cuenta que escribir en swap es más lento que una escritura normal de disco, por lo que otro factor a tener en cuenta a la hora de configurar el swappiness del sistema es cómo de rápido es el disco duro que aloja la partición de swap. En discos duros lentos, lo mejor es reducir el swappiness aún más. En cambio si los discos son muy rápidos (SSD), tener un swappiness más alto de lo normal puede ayudar a que el sistema siga funcionando correctamente en situaciones anómalas.

Recuerdo que hace muchos años, con un equipo de 4GB de RAM, muchas veces se ralentizaba al abrir unos seis programas a la vez y no dejaba trabajar. Instalé un SSD y mejoró, pero al abrir los programas seguía colgándose un poco. Cambié el swappiness a 100 y probé de nuevo: pude abrir más de 30 programas sin problema alguno. El problema era al rescatar un programa minimizado, tardaba una fracción de segundo en restaurarlo, aunque era más que aceptable.

En servidores con discos duros normales se me ha dado el caso que Apache causaba mucha presión en la memoria caché y la swap era realmente lenta. En estos casos he tenido que rebajar swappiness a 5.

No es nada recomendable poner swappiness a 0. Esto provoca que nunca se envíe nada a swap a no ser que no haya absolutamente nada de memoria disponible para los programas. Puede parecer buena idea, pero en cuanto un programa pide un poco más entonces se detiene todo y tiene que empezar a mover cantidades ingentes de memoria a swap. Hasta que no termine, el sistema estará prácticamente congelado.

La ventaja de que se mueva memoria a swap antes de que sea estrictamente necesario es que la caché se puede liberar instantáneamente, pero mover a swap toma tiempo. Si vas moviendo lo que no se usa poco a poco, cuando necesites la memoria la tendrás disponible como caché y rápidamente se libera para que la usen programas.

Aún más mala idea es no tener partición de swap, porque cuando se queda sin memoria ya no hay nada que hacer y empiezan los malloc a fallar o el oom_killer a matar procesos al azar. O ambos. Y en determinadas circunstancias, kernel panic. Es mejor que el sistema se ralentice a que explote o al menos esa es mi opinión personal, claro está. Si alguien prefiere lo contrario está en su derecho.

Continúa en el próximo episodio…

El principal punto con que quiero que se entienda es que la swap es un mal necesario. No es ni buena ni mala sino muy necesaria para asegurar la estabilidad del sistema. Ajustar los parámetros para un funcionamiento óptimo es crucial en servidores.

Con ésto sólo hemos visto una pequeña parte de cómo funciona la memoria; básicamente estamos rascando la superficie. Entender los detalles es lo que va a permitirnos comprender correctamente porqué existe el overcommit. Hay que tener paciencia.

En el siguiente artículo explicaré porqué los programas la mayoría de veces no tienen elección y tienen que cerrarse abruptamente cuando no pueden obtener memoria.

Estad atentos al blog y nos seguimos leyendo!

Linux Overcommit (II): Qué es el memory Overcommit

En el anterior artículo ya dejé caer que el Overcommit de memoria de Linux no afecta al rendimiento sin dar muchas explicaciones. Hoy voy a sentar las bases explicando el concepto de overcommit.

Qué es el Memory Overcommit

Los programas tienen dos regiones de memoria principalmente, una fija llamada “stack” y otra dinámica llamada “heap”. Cuando un programa necesita una cantidad de memoria grande o que no puede estar determinada al compilarlo tiene que solicitar al sistema operativo un bloque de memoria del “heap” con una llamada a “malloc”. La firma de esta llamada es la siguiente:

       (void*) malloc(size_t size);

Donde “size_t size” es la cantidad de bytes necesaria y el retorno “(void*)” es la dirección de memoria dónde el sistema operativo ha reservado la cantidad solicitada. Si la operación no es posible devuelve NULL.

En un sistema con memory overcommit, la llamada a malloc devuelve un puntero a memoria aún en casos dónde no hay suficiente memoria en el sistema para suplir la demanda requerida. Aún con overcommit activado podría devolver NULL en ciertos casos.

Sin overcommit, la llamada a malloc devolverá NULL cuando el total de la memoria solicitada es superior a la existente en el sistema.

En Linux, tiene tres opciones globales de configuración en vm.overcommit_memory:

  • 0 (cero) – Heurístico. Es el modo por defecto. Intentos obvios de conseguir más memoria de la existente son denegados. Bloquea ciertos usos de overcommit mientras que reduce el uso de la swap. El usuario root puede reservar un poco más de memoria en este modo.
  • 1 (uno) – Siempre permitir overcommit. Ideal para ciertas aplicaciones científicas que hacen uso del overcommit para matrices dispersas consistiendo principalmente de regiones de memoria sin usar.
  • 2 (dos) – Deshabilitar el overcommit. El espacio de direcciones total para el sistema no se permite que exceda la swap mas una cantidad configurable (50% por defecto) de la RAM física. Dependiendo de la cantidad a usar, en la mayoría de situaciones esto significa que un proceso no se matará mientras lea memoria pero recibirá errores al reservar como sea necesario. Útil para aplicaciones que quieran garantizar que sus reservas de memoria estarán disponibles en el futuro sin tener que inicializarla.

Pero esto es la cuarta parte de la historia sobre el overcommit.

Cómo se contabiliza la memoria usada

Sin overcommit, el total de memoria usada por los programas es la suma de todas las llamadas a “malloc” que no se han liberado aún con su respectiva llamada a “free”.

Con overcommit, el total de memoria usada por los programas es la suma del tamaño de todas las regiones de memoria a las tiene acceso y ha leído o escrito al menos una vez.

Dicho de otro modo: sin overcommit la memoria usada es la que has solicitado hasta que la liberas. Con overcommit la memoria usada es aquella a la que has accedido al menos una vez hasta que la liberas.

El proceso oom_killer

Sin overcommit, cuando el sistema llega al límite de memoria, malloc devuelve NULL y el proceso no tiene ninguna dirección de memoria usable. La mayoría de programas no están preparados para esta situación y abortan la ejecución.

Con overcommit, cuando el sistema llega al límite de memoria, malloc devuelve una dirección donde escribir. En el momento en que el proceso intenta escribir, el kernel invoca oom_killer en un intento desesperado de hacer hueco, selecciona qué proceso cree que hace menos falta y consume más memoria y lo mata, liberando los recursos para que el sistema siga funcionando.

oom_killer se puede configurar de muchas maneras, por ejemplo se puede ajustar para que no mate ciertos procesos con oom_adj. Se puede desactivar por completo, o se le puede decir que mate el proceso que ha pedido la memoria.

La memoria disponible

Cuando reservamos memoria, no reservamos en RAM. Se reserva contra la memoria total del sistema, que incluye la Swap:

Tanto con overcommit como sin él, la cantidad total reservable de memoria por el sistema es la suma de todos los métodos disponibles para el kernel para alojar memoria de los procesos. Si tienes memoria disponible en Swap para un malloc, incluso sin overcommit, malloc devolverá un puntero válido y el programa puede escribir en la región sin problemas.

Si a esto le agregamos módulos del kernel como zswap, la cosa se complica bastante. Cuando el kernel necesita liberar memoria RAM y quiere mover procesos a la memoria Swap, ZSwap entra en el medio, comprime la región de memoria y si ahora cabe en RAM, la deja allí. Si más tarde hay que mover más, entonces ZSwap primero mueve la memoria comprimida en RAM a Swap. Una auténtica virguería, pero ¿cuánta memoria tiene libre nuesto sistema ahora? Usar 20MB más de memoria podría suponer que la memoria libre baje sólo 5MB. Reclamar 5MB de la Swap a RAM podría usar 20MB de memoria.

Esto sólo es el principio

Debería estar ya bastante claro que overcommit sólo controla cómo se contabiliza la memoria en el sistema y por ende no puede afectar al rendimiento, sólo cómo manejarse en caso de desastre.

Pero soy consciente de que mucha gente carece de los conocimientos necesarios para entender cómo funciona la memoria en un sistema operativo a bajo nivel, por lo que he preparado más entradas para profundizar en este tema.

Estad atentos y suscribíos al blog que vienen más artículos, muchos más, para llegar hasta el final de éste asunto.

Linux Overcommit (I): No afecta al rendimiento!

Mucha gente achaca problemas de rendimiento en Linux a que hace overcommit de memoria. Yo no sé que creen que hace el overcommit, o de cómo llegan a la conclusión de que es responsable de que un servidor vaya lento. Quiero zanjar este asunto rápido y dejar bien sentado qué es y qué no es el overcommit de memoria en Linux.

Primero que nada, el overcommit cambia principalmente cómo se comporta el kernel cuando el sistema se queda sin memoria. Y cuando digo sin memoria me refiero a la total imposibilidad de reservar memoria para un malloc (sin overcommit) o al escribir memoria nueva (con overcommit). Y por “total imposibilidad” nos referimos a que no es posible hacer ninguna de las siguientes:

  1. Asignar más memoria en RAM.
  2. Liberar más memoria caché (RAM).
  3. Asignar más memoria en Swap.
  4. Obtener más memoria por cualquier otro método que el kernel pueda tener activo (compresión, etc) sin comprometer la estabilidad del sistema.

Si tienes dos gigas libres en Swap, tu equipo tiene memoria libre de sobra a efectos prácticos para el overcommit y el comportamiento entre tenerlo activado o no es nulo.

Si en cambio el sistema se ha quedado realmente sin memoria, la diferencia es que con overcommit verás un proceso llamado “oom_killer” que se vuelve majara matando procesos para liberar memoria y llena el syslog detallando lo que hace. Si overcommit está desactivado, las peticiones que superan la memoria restante devolverán error y los programas que las hacen mueren repentinamente a no ser que sepan manejar la situación, lo cual es raro.

Cuando el sistema se ha quedado totalmente sin memoria la situación es terrible y muchas veces irrecuperable. Tener overcommit o no es elegir qué tortura queremos en tal caso. Pero en ambos casos el resultado suele terminar en inestabilidad y posterior reinicio manual de la máquina, excepto si se han llevado a cabo las medidas pertinentes para que el sistema sea estable en éstas condiciones. (Aunque el significado de “estable” difiere según lo que se espere del equipo en cuestión)

Quiero que quede bien claro, así que insisto de nuevo: Que el sistema se ralentice cuando se queda sin memoria RAM no tiene tiene absolutamente nada que ver con el overcommit.

Explicar correctamente el porqué me va a llevar un buen tochazo, desde explicar qué es el overcommit, qué cambió con los 32bits y el sistema de memoria virtual, qué son los page faults, los memory mapped files, qué es el lazy memory allocation, cómo funciona la memoria swap, cómo funciona la reserva de memoria en un proceso a la práctica, qué es (de nuevo) el overcommit hasta ver con detalle la diferencia interna entre usar y no usar overcommit.

Para evitar publicar 7000 palabras he preferido dividir el artículo en entregas. Recomiendo leerlo todo para entender realmente el problema y espero que al haberlo dividido sea más fácil recordar por dónde os quedasteis leyendo.

P.D.: No vengo a defender el overcommit, por mucho que lo parezca. Explicaré más adelante dónde el overcommit es una mala idea y comparo el comportamiento del equipo en distintos escenarios. Como con todo, es bueno y malo a la vez, depende de qué uso se le dé. Mi intención es únicamente desmitificarlo.

Cómo se generan números al azar en un programa de ordenador

Has pensado alguna vez, si un ordenador es como una calculadora, ¿cómo se generan números al azar? Todas las operaciones que un ordenador hace son concretas, deterministas y dos más tres siempre son cinco. Cómo hacen para generar cada vez un número diferente si todas las operaciones siempre tienen un resultado fijo?

En este artículo, para cambiar un poco, quiero volver a lo más básico. Para aquellos que están empezando a aprender a programar puede ser interesante ya que, el azar como tal no existe en los ordenadores y sin embargo consiguen generar números aleatorios.

En informática usamos “aleatorio” en lugar de “al azar”, pero vienen a significar lo mismo. Aleatorio es cualquier cosa que se deja al azar. Un número aleatorio es un número al azar. Como internamente los ordenadores trabajan con números, lo que nos interesa son números al azar. De ahí se derivan otras cosas como palabras o fechas al azar. Una vez tienes tu número aleatorio es relativamente fácil convertirlo en fechas, textos o cualquier otras cosas que necesites. Generar el número inicial y que sea aleatorio, no es tan sencillo.

Pero primero, ¿qué es un número aleatorio? Pongamos que queremos simular un dado de seis caras, por lo que en este caso particular vamos a querer números del uno al seis. Entonces hacemos una función para que devuelva nuestro número aleatorio:

Hemos lanzado un dado, salió cuatro y lo hemos apuntado en nuestra función. Ahora cada vez que llamamos a la función devuelve cuatro, que es el resultado de un proceso aleatorio como lanzar un dado. ¿Dónde está el problema?

Siempre devuelve el mismo valor. Un número aleatorio debe cumplir por lo menos una regla básica: no debe ser predecible. Si siempre devuelve cuatro, sabemos que la siguiente llamada devolverá cuatro con un 100% de certeza. El origen del número puede ser aleatorio, pero el proceso que genera el número en este caso es muy simple y ya no es útil como número al azar.

Otras ideas, como llevar una cuenta de las veces que se ha llamado a la función puede evitar que siempre devuelva el mismo número, pero siguen siendo igualmente muy sencillas de predecir:

int counter = 0;

int getRandomNumber() {
  counter += 1;
  if (counter > 6) {
    counter = 1;
  }
  return counter;
}

Con este código de ejemplo, lo que devuelve es una secuencia “1,2,3,4,5,6,1,2,3,…”. No devuelve siempre el mismo número, pero sabiendo qué número devolvió antes, puedes saber qué número va a generar después.

Nuestro primer generador de números

A partir de aquí hay que tirar un poco de matemáticas para subir el listón. Por ejemplo, existen los números irracionales. Éstos no pueden ser expresados como fracción y tienen una cantidad infinita de decimales que no siguen ningún patrón. Esto es ideal para números aleatorios, ya que al no seguir patrones no se puede predecir el siguiente número. Algunos números muy conocidos son Pi (π), e y las raíces cuadradas de algunos números.

Por ejemplo, si partimos de Pi (π) podríamos hacer:

int N = 0;
char pi[] = "314159265358979323846264338"
    "32795028841971693993751058209749445"
    "92307816406286208998628034825342117"
// ... omitidos un millón de dígitos ...

int getRandomNumber() {
  N += 1;
  if (N > 1000000) {
    N = 0;
  }
  // Devolver el dígito N de Pi 
  // como número (pseudocódigo):
  return int(pi[N]);
}

Ahora la función devuelve números entre 0 y 9. Sabiendo que devolvió un 5, no podemos saber con certeza si luego viene un 3, un 8 o un 6. Entonces, ya lo tenemos ¿verdad?

Si bien es cierto que ésta forma de generar números aleatorios ya es útil en algunos casos (muy pocos), no está exenta de problemas.

Imaginemos que usamos éste método para hacer un juego de rol, donde el jugador tiene que lanzar un dado de diez caras (del 0 al 9) para ver si ciertas acciones serán más o menos efectivas. Por ejemplo, al disparar con un arco se lanza un dado. Si da cero, el tiro no da en el blanco. Si da nueve es un crítico y hace doble de daño. No tiene mala pinta, ¿verdad?

El problema es que el jugador puede aprenderse las secuencias y darse cuenta que el juego, cuando arranca siempre da “3” como primera tirada de dado, luego “1”, luego “4”, etc. Y eso, ¿de qué sirve? Sencillo, las tiradas de inicio son “3,1,4,1,5,9”. Cuando el jugador llega a una pantalla difícil, sólo tiene que guardar partida, salir, arrancar el juego de nuevo y ya sabe que el crítico lo tiene en la sexta tirada. Disparando antes cinco veces, sabe que el sexto disparo va a ser crítico. Y puede abusar del sistema para que los críticos aparezcan dónde le interesan.

Para evitar que esto ocurra, necesitamos que el programa cuando arranque tenga ya un estado impredecible. A esto se le conoce como “semilla” del generador (seed) que es la que determina cómo se van a generar los números:

int N = 0;
char pi[] = "314159265358979323846264338"
// ..... omitidos un millón o dos de dígitos ....

int getRandomNumber() {
  // (...)
  return int(pi[N]);
}

void setRandomSeed(int n) {
  N = n;
}

Sólo tenemos que llamar cada vez que arranca el programa setRandomSeed con un número distinto. Fácil, ¿no? Bueno… ¿de dónde nos sacamos un número distinto cada vez?

Tenemos la suerte de que los ordenadores también cuentan con un reloj. El sistema cuenta con una llamada que nos devuelve la fecha y hora actual. Y ésta no se repite, porque dos llamadas, por muy juntas que sucedan, no son simultáneas. De este modo si usamos la fecha y hora para setRandomSeed tenemos cierta garantía de que cada vez será distinto.

En muchos lenguajes de programación, en lugar de devolver la fecha y hora en un formato comprensible, como “19-06-2010 20:05:35”, devuelven algo llamado “Unix Timestamp” que es la cantidad de segundos desde el 1 de Enero de 1970 (Unix Epoch).

Se puede convertir fácilmente un Timestamp en un formato de fecha comprensible. Como lo que queremos aquí es un número nos viene muy bien el formato que devuelve, ya que no nos interesa saber qué fecha es, sino tener un número distinto cada vez. En C sería:

time_t currentTime = time(NULL);
setRandomSeed( (int) currentTime );

Pero ahora, aunque cambie cada vez, sólo cambia una vez por segundo y sólo se mueve un dígito a la derecha en los decimales de Pi por cada segundo. Aunque dificulta bastante deducir qué número saldrá después no es del todo imposible. Además, si el jugador hiciera de media dos tiradas de dado por segundo, al reiniciar el juego el generador de números volvería hacia atrás, por lo que si el jugador apuntó qué resultados obtuvo, sabrá qué valores tendrá al reiniciar el juego. Puede parecer rebuscado, pero la gente hace cosas muy rebuscadas con tal de ganar.

La solución es usar un contador que vaya muchísimo más rápido que los segundos. Pero en C, a diferencia de otros lenguajes, no tiene una forma estándar de hacerlo. El siguiente código funciona en casi todos los sistemas excepto en Windows y devuelve el número de microsegundos desde el epoch:

long getMicrotime(){
	struct timeval currentTime;
	gettimeofday(¤tTime, NULL);
	return currentTime.tv_sec * 1000000
            + currentTime.tv_usec;
}
setRandomSeed( getMicrotime() );

En Javascript y otros lenguajes más modernos es bastante más sencillo. Pero no importa. Con esto es más que suficiente para que sea impredecible el valor de nuestro generador, ¿verdad?

Pues no, por dos motivos. El primero es que los relojes de los ordenadores no suelen medir con resolución de microsegundos, por lo que siempre tenderá el número a terminar con 1000, 2000, o algo similar. Esto quita mucho trabajo a la hora de buscar dónde puede estar comenzando el generador ya que salta en bloques. Pero de momento dejaremos pasar este problema porque es menos grave que el que explicaré ahora:

Como estamos almacenando los dígitos de Pi en el programa, la memoria del ordenador tiene un límite, por lo que a la práctica almacenaremos sólo algunos millones de dígitos. Aunque dado un sólo dígito no podamos predecir el siguiente, sí que es posible que, dada una secuencia de dígitos aleatorios podamos localizarlos dentro del millón. Porque un dígito aparece muchas veces de maneras distintas, pero si nos centramos en secuencias de seis dígitos consecutivos, éstas no se repiten tanto.

Dicho de otro modo, la probabilidad de que un dígito al azar de Pi sea 9 es 1 entre 10 posibilidades. Pero la probabilidad de que diez dígitos consecutivos sean 012345 es 1 entre 10 elevado a 6. Es decir, una entre un millón. Eso quiere decir que estadísticamente, en el primer millón de dígitos, una secuencia cualquiera de seis números consecutivos se repite una sola vez. A veces se repetirá dos, o tres veces. Otras secuencias no aparecerán nunca.

Lo importante es que si el jugador apunta el resultado de seis tiradas consecutivas, puede ir a una tabla y localizar en qué punto está el generador. Esta tabla puede ser grande, pero podría estar usando un programa de ordenador para facilitar la búsqueda.

No todos los aleatorios son iguales

Llegados a este punto, sabemos que este generador es bueno para ciertas cosas pero no para otras. Por ejemplo, si lo usamos para un juego de Tetris para decidir al azar la siguiente pieza no habría demasiado problema. Aunque el jugador pueda saber con esfuerzo la pieza que le saldrá 15 o 20 tiradas por delante, no le va a servir de mucho. (de todos modos en Tetris aparece ya la siguiente pieza)

Los números aleatorios también se usan para temas de seguridad, por ejemplo para elegir la llave de cifrado para un certificado de la web (https). En este caso, si alguien es capaz de saber qué números devolvió el generador durante la creación del certificado, podrá hacer una copia de la llave privada, permitiéndole interceptar y descifrar todas las comunicaciones.

Hacer un buen generador de números aleatorios no sólo lleva trabajo, también son más lentos. Hay algoritmos de números aleatorios tremendamente rápidos que pueden generar millones o incluso billones de números al azar en un segundo, pero no son apropiados para crear certificados HTTPS. Otros son muy buenos y son casi imposibles de predecir, pero son muy complicados y lentos, por lo que no se pueden usar para todo.

Sean mejores o peores, todos los generadores a la práctica son deterministas: Sabiendo el valor del estado, sabes qué va a generar. Por eso mismo se llaman pseudo-aleatorios. Porque si un evento fuese realmente al azar, no podrías saber qué valores va a producir ni aunque supieras la posición y estado de todos los átomos del universo.

Esto quiere decir que cosas cotidianas como lanzar una moneda al aire o lanzar un dado son en realidad procesos pseudo-aleatorios: Si el lanzador fuese capaz de lanzar los dados de una forma exacta y tener en cuenta cómo rebotan en la mesa, podría controlar el resultado de la tirada. Cosas como la humidad o la temperatura pueden afectar cambiando los rebotes y hacen casi imposible la hazaña. Pero no es imposible. El problema es que hay demasiadas variables a tener en cuenta como para poder hacer un seguimiento de todas y realizar una predicción.

Para obtener números pseudo-aleatorios totalmente impredecibles se usa la misma idea: juntar tanta información externa como sea posible para que no haya forma de que alguien pueda tenerlo todo en cuenta. Por ejemplo, apuntar las centésimas de segundo entre las pulsaciones de teclas del usuario, los movimientos del ratón, el tiempo que tarda el disco duro en buscar un fichero, la fecha y hora actual, etc.

Este proceso de agregar más información cuasi aleatoria se le conoce como aumentar la entropía del sistema (entropy pool) y usan algoritmos que al darles datos al azar los usan para aleatorizar más y más un estado interno. A la cantidad de aleatoriedad que contiene este estado interno se le conoce como entropía.

Cuando los números aleatorios se usan en temas de seguridad nos volvemos muy paranóicos, hasta el punto de contar la cantidad de entropía que tiene el sistema, descontar ésta entropía cada vez que emitimos un número e impedir que llegue a cero. De este modo el estado interno siempre va variando al azar conforme se generan los números y si detectamos que estamos generando demasiados números desde el mismo estado bloqueamos o damos un error, antes que permitir que al dar demasiados números pseudo-aleatorios desde un mismo estado un atacante pudiese deducir el estado original.

Pero aún con todas estas medidas, el generador sigue siendo pseudo-aleatorio. ¿Existen los generadores realmente aleatorios? Sí, pero se basan en efectos cuánticos. En la física cuántica los eventos suceden con una probabilidad, pero es totalmente aleatoria y no se puede predecir exactamente en qué orden o cómo van a producirse. Sólo podemos calcular las probabilidades de que un evento ocurra.

Se venden tarjetas externas para el ordenador que generan números aleatorios. Se basan en ruidos de fondo, por temperatura, por efectos termoeléctricos o incluso radiación. Normalmente no se usan porque para la mayoría los generadores pseudo-aleatorios son más que suficientes. Sólo se usan en algunas aplicaciones donde la seguridad es muy importante y tienen que generar unas cantidades ingentes de números aleatorios seguros.

Concluyendo

En definitiva, lo importante es saber que no todos generadores de números aleatorios sirven para todo. Algunos son más rápidos, otros son más seguros.

A la práctica, los generadores pseudo-aleatorios no usan números irracionales sino funciones caóticas. Los números irracionales (como Pi), aunque son un buen origen de números, los dígitos cada vez cuestan más de calcular, por lo que no es práctico calcular los dígitos al vuelo.

Las funciones caóticas, inspiradas en los cálculos meteorológicos, son aquellas en las que pequeñas variaciones producen cambios impredecibles. Como lo que se dice: “el leve aleteo de las alas de una mariposa se puede sentir al otro lado del mundo”. Por ejemplo los péndulos dobles son uno de los ejemplos más sencillos de un sistema caótico. Suelta dos veces el péndulo en el mismo lugar y hace cosas distintas:

Por eso mismo se usan este tipo de matemáticas en la generación de aleatorios: una pequeña variación en la semilla y produce números completamente distintos e impredecibles. Y es muy rápido de calcular, especialmente si lo comparas con calcular los dígitos de Pi.

Espero que os haya servido para entender mejor cómo se generan los números al azar en el ordenador y ver los diferentes problemas que hay en ello. Nos seguimos leyendo!

Actix-web is dead (about unsafe Rust)

Update 2020-01-20: Actix oficial web repository is back and the maintainer has stepped down. Actix will continue to be maintained.

Recently the maintainer of Actix webserver took down the GitHub repository and left the code in his personal repository, deleting lots of issues, enraging a lot of people. He left a post-mortem:

https://github.com/actix/actix-web/blob/7f39beecc3efb1bfdd6a79ffef166c09bf982fb0/README.md

What happened? I did my own read of the postmortem, and from Reddit I also found this article which summarizes the situation pretty well:

https://words.steveklabnik.com/a-sad-day-for-rust

To summarize it in a few words in case you don’t feel like reading those: Rust community is heavily focused on a safe use of Rust where proper memory handling can be proven. For Rust, unless you use the “unsafe” keyword, the compiler guarantees no memory errors in a provable way, so usually for those small parts where the compiler is unable to prove the code, it’s okay to use “unsafe”. The remaining code should be small and easy to prove correct.

Actix was found by third parties abusing unsafe and when they were auditing most libraries found for Rust on the internet. When the unsafe code was audited it was found that on misuse, it can lead to serious vulnerabilities. So they opened a bunch of issues and added a lot of patches and PR’s in GitHub.

The response from the maintainer was that he doesn’t care, didn’t accept almost any of the patches, deleted the issues and the conversation heated up a lot and finally he deleted the repository itself from the official source and left it under his own username.

This is sad. Actix was known by its amazing speed on different benchmarks and was used by a lot of people. While it’s bad that the community sometimes is too harsh and some people lacks a lot of politeness (which makes maintainer life really hard), I’m going to be polemic here and say: It’s good that this happened and Actix-web got deleted.

I have been using Actix-web, seduced by its speed and I never thought I could be promoting a vulnerable webserver. I was assuming that because the library was coded on Rust, the author was taking care of not using unsafe where possible. But I was so wrong. Luckily I had other things to do and never released the article where I was going to promote Actix-web. Now I’ll have to redo the benchmarks before releasing anything.

The same happened for lots of other people, and all those uses combined, Actix-web has increased the surface area of attack for a lot of deployments.

I would have argued in other cases that for certain use cases, having software that prioritizes speed to security is good on certain scenarios where the inputs or the environment is not exposed to the internet. But this is a webserver, it’s main job is to serve as a facade for the internet. But even the project documentation never mentioned this aspect that the target was just to make the fastest webserver even if that meant to sacrifice security.

There’s no point on running Actix-web behind anything to reduce its potential problems: It is several times faster than raw Nginx or Apache serving static content. Adding anything on front will slow it down a lot. Also, there’s no reason to use it for internal networks: If it’s just serving HTTP to internal users, any web server will do, as internal networks have much less traffic. If it’s used to pipe commands along several machines, then HTTP is just a bad choice. use RPC’s instead like gRPC.

To be completely fair let me state that Actix-web never had a real issue as far as I know. It’s just that its correctness cannot be proven. Is this a problem? For me, yes, because if I wanted otherwise I would go with C or C++ instead. There are lots of really good, really fast web servers using raw C++. The point of using Rust in the first place is having memory guarantees, like using Java but without paying the performance penalties.

I understand that the maintainer just wanted to have fun with coding and there’s nothing wrong with that. But when your product starts getting recommended by others you have to care. This can’t be avoided: with great powers come great responsibilities.

This is the good thing with the Rust community. They’re fully committed to even inspect the sources of every single library out there and help by reducing the amount of unsafe code, even patching them.

It’s sad that the repository has been “deleted”, but this is good for Rust. Quality needs to be there and definitely they need to prevent unsafe code from gaining ground. There’s no point of having Rust if most libraries you can practically use are memory unsafe.

To conclude this: please be polite, everyone. It’s quite hard when you get a ton of people bashing at you. But also, keep the good job up!

I’m not buying a new computer (yet) – Is Moore’s law dead?

I remember when I was a kid and went to school that computers were lasting roughly four or five years. After that, the market shifted so much that we needed a new computer. Even if the old computer was a really expensive one, still on five years the advance was so big that the upgrade was absolutely needed.

I started noticing some years ago that some old servers from a customer were actually more powerful than the cheap ones that we were buying at the time. For the people that is new to IT or not familiar with it this might make sense, if you buy a 3000€ server should be more powerful than a 500€ one. But for those that like me followed the market since the Spectrum (you know, those cassette computers mainly used to play games on a TV) this was new. Every 3 years or less performance doubled, and the performance difference between a mid-range computer and a top-notch was around 50%. This server I’m talking about was running KDE 3.5, and had the same Debian installation ever since. 8 years later, the computers should have gone at least 300% more efficient yet the processor it was using was faster than the low-range server CPU’s that I was seeing at the time.

Fast forward to months ago where I was comparing my current computer to the ones from my friends and I noticed it’s quite a bit slower, around 30%. And I thought, well maybe it’s about time to change it because it’s kinda old. Wait one sec…. how old?

It’s so old I cannot even remember when I got it. I built it myself using a used CPU from a friend who sold it to me. So I had to lookup the CPU on Google, it’s an i7-920:

https://ark.intel.com/content/www/us/en/ark/products/37147/intel-core-i7-920-processor-8m-cache-2-66-ghz-4-80-gt-s-intel-qpi.html

It says it was out in late 2008 so probably the computer is from 2009 or 2010. Roughly 10 years old… of course I cannot remember!

I’ve been upgrading it ever since, it has its 3rd PSU, added a GTX 1060 6GB, replaced its RAM with 16GB and added a 1Tb SATA SSD. Works like a charm… well, it doesn’t. This CPU has been problematic since day one, with stability problems. The weight of the CPU cooler has bent something and now I have 2 dimms out of commission (I bought 24GB of RAM but only 16GB work).

So I went to PcPartPicker website and did a few list of components. Because I need a complete new one for Lucía, I don’t plan to reuse any of my original components. And this is what I got:

PCPartPicker Part List: https://ie.pcpartpicker.com/list/qqD8HB

CPU: AMD Ryzen 5 3600 3.6 GHz 6-Core Processor (€234.95 @ Komplett)
Motherboard: MSI X570-A PRO ATX AM4 Motherboard (€194.99 @ Custompc)
Memory: G.Skill Ripjaws V Series 32 GB (2 x 16 GB) DDR4-3200 Memory (€164.44 @ Custompc)
Storage: Samsung 970 Evo 1 TB M.2-2280 NVME Solid State Drive (€198.95 @ Komplett)
Video Card: MSI GeForce GTX 1660 Ti 6 GB VENTUS XS OC Video Card (€338.95 @ Komplett)
Case: Fractal Design Meshify C ATX Mid Tower Case (€99.94 @ Komplett)
Power Supply: Corsair RM (2019) 750 W 80+ Gold Certified Fully Modular ATX Power Supply (€109.85 @ Komplett)
Total: €1342.07
Prices include shipping, taxes, and discounts when available
Generated by PCPartPicker 2020-01-04 08:36 GMT+0000

And that was more expensive than I thought. I don’t remember building any computer over 900€. So the question is, how fast is this new computer compared to the old one?

https://cpu.userbenchmark.com/Compare/Intel-Core-i7-920-vs-AMD-Ryzen-5-3600/1981vs4040

Well, the CPU it’s twice as fast… wait a second. 11 years after, only 2x faster? Well that’s surely because I’m selecting a mid-range CPU and the i7 was more expen… crap:

https://cpu.userbenchmark.com/Compare/Intel-Core-i7-920-vs-AMD-Ryzen-7-3800X/1981vs4047

But at least after 10 years even the budget CPU should have got insanely better than mine, right? right?

https://cpu.userbenchmark.com/Compare/Intel-Core-i7-920-vs-AMD-Ryzen-3-3200G/1981vsm824486

Well, yes. More or less. What about the GPU? I got my 1060 around 3 years ago, but I only see a 1660 Ti with a similar price:

https://gpu.userbenchmark.com/Compare/Nvidia-GTX-1060-6GB-vs-Nvidia-GTX-1660-Ti/3639vs4037

Only 24% more performance in 3 years for roughly same price. And that’s disappointing because I’m not willing to upgrade for just that. The problem is, if I build a new computer I’m going to need a new graphics card for Lucía. So I’ll need to wait on this.

What about the SSD? I don’t have a M.2 slot on this old motherboard, so moving to that should give me a huge performance boost.

https://ssd.userbenchmark.com/Compare/Samsung-850-Evo-1TB-vs-Samsung-970-Evo-NVMe-PCIe-M2-1TB/3576vsm494791

2.7x times faster is quite a bit! I bought the old one two years ago and I’m sure I spent almost 300€. The new one is cheaper, smaller and really fast. Good news, at last.

So despite having a computer that is 10 years old I’m still not buying a new one. Lucía has a laptop that I bought used around 6 years ago and I upgraded the RAM for a few bucks a few months ago, and it’s still up to the task. The main issue is the fan which makes annoying noises. But still when I go to the shop I see a lot of cheap/bad hardware that isn’t even what she has at the moment, and spending nearly 1000€ on a new laptop that isn’t much better is not something I’m willing to do.

This picture started changing in December 2019, where AMD started releasing new hardware like crazy and prices have started falling. I’m seeing now some interesting deals at the moment. But I feel this is going to get way, way better during 2020.

Is Moore’s law dead? No. Several times in the past we hit a “barrier” were technology stalled for some years with small improvements, but afterwards it always bounced hard with huge changes in small amounts of time. I believe we’re exiting a long period of technology stagnation.

In CPU’s, Intel had lots of problems in the last 8 years, not delivering up to speed, and AMD was way behind to compete. After that period AMD not only caught up, it also surpassed Intel with no signs of waiting for anyone.

In Graphics cards, we saw a huge increase of prices and a shortage of cards due to the popularity of cryptomining. This almost finished now, AMD is also catching up with NVIDIA and we’re seeing again some improvements. NVIDIA new generation of cards is predicted to get out in this year, and AMD should be releasing new cards soon too. This competition should drive prices down.

On the other hand, SSD had problems 5 years ago with shortages and not much popularity, but in the last 2 years they have gained a lot of traction. We can see now crazy SSD speeds and capacities at competitive prices.

For the record, watch closely this video on Moore’s law comparison with actual transistor count. Look closely on the stagnation periods and comebacks:

So, no, I’m not buying a new computer… yet. This year promises to be great in technology advance, and when I buy the next computer I would like it to last another… 10 years? Hopefully!

My predictions for tech in 2020

Happy new year 2020! A lot of things happened in 2019, and this new year is surely going to surprise us even more. This is not a strict prediction for a single year, but in between of a recap of the last months and a predicion for forthcoming years.

AMD overtaking Intel

With the last releases of the 3rd generation chips, AMD has already overtook Intel in 99.9% of the market, from the cheap to the enthusiast, and even on servers. Intel only holds one chip that is slightly more performant (3%) in single thread scenarios, which only make sense for gaming.

But, AMD is soon to release its 4th Generation chips, and with these we expect a performance increase between 20 and 50 percent (!!) With this, AMD will have a big gap against Intel and there will be almost no reasons left to go with it.

Will Intel respond in time? I don’t think so. They need to start from scratch, so many bugs hitting their CPU and they are taking too much time to get into 7nm. AMD got a better strategy in the past years in the design of their chips and they will be hard to catch up.

Intel is presenting new chips soon too, but they seem just the same thing with slightly more cores and more frequency. And these are rated at 125W TDP. Most probably they’re placing the TDP lower than it actually is. Intel chips in contrast to AMD have all heat concentrated in a really small area which is really complex to dissipate properly. So I expect those to have no room at all for overclocking and having problems with most cooling systems (i.e. not performing at their boost speeds because of the lack of cooling)

In the other end, the AMD’s ThreadRipper socket makes much more sense. Being so big and its cores spread on the die makes the heat much easier to remove. I’m amazed how some coolers like Noctua are handling those TDP. Even server chips like Epyc Rome with a TDP of 250W can be cooled easily on a server using a passive cooler and the forced airflow of the case. It’s quite impressive.

Meanwhile, TSMC says 5nm is coming in 2020. This is AMD provider, so 5th generation is better than “on-track”. I don’t think we will get them in 2020, but probably in 2021. This will add a huge boost of thermal performance, allowing for chips to do much more work with way less heat output.

PCI Express 4.0 is already here

So AMD has released in last months chips capable of PCIe 4.0 and this is a huge deal, more than we can think of now. With the amount of PCIe lanes that those chips pack, we’ll see some interesting stuff.

There are two components that can benefit from this: M.2 SSD drives and Graphics cards for AI training.

Graphics cards have to load the training data from the systems memory, and some of the training types see a huge bottleneck here. As for now, there are no Nvidia cards with PCIe 4.0 yet, and it does not make much sense for regular consumers unless it’s for marketing purposes like AMD is doing. But AI training is a different story. On this year 2020, probably at the end of the year, I would like to see a proper PCIe 4.0 card from NVIDIA aimed for AI.

On M.2 SSD there’s already people taking advantage of the extra speed doing crazy RAID setups to go up to 300Gbps where the CPU itself is the bottleneck now:

There are cheaper, more consumer-alike solutions like PCIe cards that allow for 2-4 M.2 drives. For consumers a faster M.2 will be a better option, and we will keep seeing faster and faster drivers in the next years.

HDD to be used for archiving only

Most data shows that SSD drives will remain more expensive than HDD ones for a long long time. But the market has already started to switch to SSD. We started seeing enthusiast laptops to include boot SSD and an HDD for data, but we will start to see soon a combination of M.2 and SATA SSD. And finally just M.2.

There is a big missed point when predicting the SSD takeover and HDD fall. Drives don’t get cheaper but bigger in size at the same price. We never see HDD at 20 eur. On the other hand, there are SSD at that price point. That’s because plastic and a board is cheaper to manufacture than steel, plates and complex high-precision manufacturing.

This has two effects. On the lower end of the spectrum, prebuilt computers at some point would have to choose between shipping 1TB SSD or 4TB HDD. Or in the far future, 4TB SSD or 12TB HDD. At some point in here will not make sense anymore to put so much capacity as it will not be the main driver for consumers to get a unit, so using a faster drive will be the selling point.

On the higher end, for servers, packing so much data into a single drive will start causing problems. HDD speeds are not increasing nearly as much as capacity, so writing or reading them takes longer and longer each year. At some point this will become so slow that the only reasonable use is “write and forget”, like tape drives but with random access capabilities.

Will this happen during 2020? The market has a lot of inertia. There exist a huge amount of servers prepared for hard drives and not SSD, so it will take 5-10 years for the new servers to start overtaking the old ones. But certainly it will change a noticeably amount in a year or two.

But the HDD will start seeing way less sales from now on, and with less revenue there will be less money available for research on them, breaking the predictions they made for the next 10 years.

Linus recently talked about who was asking for 20TB drives and its problems. Think about what happens if they try to sell 40TB:

I don’t think they have any room left to make them cheaper. But they will still be a great choice for storing big files and backups for a long, long time.

Graphics card price drop

We had a rough 2018 and 2019 in graphics card pricing, when it rocketed from Bitcoin mining and similar cryptocurrencies. Now that the market is back to normal, and AMD is pushing hard with their pricing, NVIDIA first responded releasing a ton of cards that were filling all gaps at all possible price points, but AMD still holds better value than NVIDIA. Also AMD plans to disclose new cards very very soon. This will force prices back down to something affordable to the consumer.

There are a few rumors of a RTX 3080 coming up in mid-2020, and the timing is about right. It should be 7nm, which compared to the current 14nm should be a huge boost. If that’s the case, by the end of 2020 we should see a good price deal for 2060 graphic cards.

On ray-tracing, I expect AMD to add some form of compatible ray-tracing. Having seen how ray-tracing simplifies the development of games, it’s probable that in five years we start to see some games intended for ray-tracing only with limited support for old cards.

Monitors with high refresh rate

We have seen a long trend the last months with really good new IPS technology for low latency, high refresh rate IPS monitors. They have become way cheaper now. I got myself a new ASUS PB278QR 27″, still 60Hz, but thanks to the price drops I got it at Amazon for only 320€, and it has a 1440p resolution.

Sadly, the 1440p resolution is not as common as 4K (probably not as good for marketing) so there’s less to choose from, specially on laptops which seem to jump from FullHD (too low for my taste) to 4K (too high for a small display). Hopefully with next year new graphics cards it will make more sense to jump directly to 4K.

Happy new year!

Let’s see what 2020 has, I really have my hopes up. Last month was a really interesting one, and it seems it will continue for long.

For now I’ll keep my old computer, probably for the most part of 2020. I need a new one from years ago, but the old one still works pretty fast and the upgrade it’s not worth the money for me. If the industry keeps this pace that will change in a few months. We’ll see!