Sobre stackalloc, o (mejor) Homenaje a John Horton Conway

El pasado fin de semana estaba empezando a escribir este artículo al mismo tiempo que miraba las noticias, que últimamente no traen nada bueno. Ahora que se me van acabando las novedades recientes de C# de las que no he hablado todavía, y que no me atrevo a decir nada sobre aquella de la que no me siento preparado para hablar – léase los tipos-referencia anulables (nullable reference types), el artículo iba a estar dedicado al operador stackalloc, que con la última versión del lenguaje ha salido del oscuro nicho del código no seguro (unsafe code) para pasar a poder ser utilizado en escenarios más comunes.

Esencialmente, al utilizar stackalloc en lugar de new al inicializar un array local dentro de un método, se le está indicando al compilador que reserve la memoria para ese array en la pila de ejecución en lugar de crear un objeto heredero de Array en la memoria dinámica, como ocurriría al usar new. En C# 8.0, ese vector en la pila debe manipularse (fuera de un contexto no seguro) a través de la clase Span<T> de la que ya hablamos hace un tiempo aquí. En muy diversas situaciones, el alojamiento en la pila podría producir mejoras en el rendimiento, ya sea debido a la mayor velocidad de adquisición de la memoria (que en el caso de la pila se reduce a mover un puntero), a la localidad de los datos que el método manipula, o a la disminución en las necesidades de ejecución del recolector de basura.

Una vez claros los fundamentos teóricos en los que se basa la característica, tocaba lo más difícil: encontrar un buen ejemplo que hiciera evidente esas ventajas. Y entonces levanté los ojos para mirar las noticias, donde presentaban a personajes más o menos conocidos que perdieron la vida recientemente a causa del brutal COVID-19. Entre ellos estaba John Horton Conway, el genial matemático británico que creó esa maravilla de máquina universal de Turing que es el Juego de la vida (Game of Life). El Juego de la vida forma parte indeleble de mis memorias de juventud: aún recuerdo el brillo en los ojos de nuestros alumnos de Programación en la Universidad de La Habana cuando les explicábamos1 en qué consiste el juego y luego los veíamos pasarse horas implementándolo en aquellos IBM PC originales (para los que la fecha inicial siempre era el 1 de enero de 1980), utilizando esa otra maravilla de la tecnología de su tiempo que era Turbo PASCAL 3.0 (¿alguien recuerda el número 39671?).

El fichero que se adjunta al final del artículo contiene una implementación en C# del Juego de la vida, adaptada de la que se ofrece en Rosetta Code. El método que crea una nueva generación a partir de la actual es el siguiente:

private void UpdateBoard()
{
    // A temp variable to hold the next state while it's being calculated.
    Span<bool> newBoard = new bool[Width * Height];

    for (var y = 0; y < Height; y++)
    {
        for (var x = 0; x < Width; x++)
        {
            var n = CountLiveNeighbors(x, y);
            var c = board[x + y * Width];

            // A live cell dies unless it has exactly 2 or 3 live neighbors.
            // A dead cell remains dead unless it has exactly 3 live neighbors.
            newBoard[x + y * Width] = c && (n == 2 || n == 3) || !c && n == 3;
        }
    }

    // Set the board to its new state.
    board = newBoard.ToArray();
}

Para adaptarme a las limitaciones de stackalloc, he introducido a Span<T>, he convertido a posta la estructura de datos newBoard (originalmente una matriz) en un array, y me he echado encima el cálculo de la posición en memoria de los elementos. Todo eso hace posible pasar de una versión que usa new para reservar la memoria a una que utiliza stackalloc cambiando sólo la primera línea de código del método:

    Span<bool> newBoard = stackalloc bool[Width * Height];

Con el objetivo de comparar el rendimiento de ambas variantes, el programa de ejemplo ejecuta bajo condiciones repetibles 250 juegos sobre un tablero de 100*100 celdas. Invito al lector a que repita el experimento y vea si obtiene resultados diferentes al mío. En mi viejo Mac, con la optimización de código activada y ejecutando sin depuración, la variante que usa new requirió 694 segundos, mientras que la variante basada en stackalloc consumió 676. No puedo asegurar que estos números den una respuesta concluyente sobre las ventajas de usar stackalloc, pero eso lo dejo también al análisis del lector interesado. A fin de cuentas, el objetivo de este artículo no era tanto hablar de programación como servir como modesto homenaje a la vida y obra de John Horton Conway. God rest his soul!


1 Me refiero aquí a los miembros del equipo docente dirigido por mentor y amigo Miguel Katrib.

Código fuente:

using System;

namespace Conway
{
    // Plays Conway's Game of Life with a random initial state.
    public class GameOfLifeBoard
    {
        // The dimensions of the board in cells.
        private const int Width = 100;
        private const int Height = 100;

        // Holds the current state of the board.
        private bool[] board;

        // Creates the initial board with a random state.
        public GameOfLifeBoard(int randomSeed = 0)
        {
            var random = randomSeed == 0 ? new Random() : new Random(randomSeed);

            board = new bool[Width * Height];
            for (var y = 0; y < Height; y++)
            {
                for (var x = 0; x < Width; x++)
                {
                    // Equal probability of being true or false.
                    board[x + y * Width] = random.Next(2) == 0;
                }
            }
        }

        // Play the game until the colony extinguishes or maximum iterations is reached
        public bool Run(int maxIterations)
        {
            int i = 0;
            while (i < maxIterations & !IsEmpty)
            {
                UpdateBoard();
                i++;
            }
            return !IsEmpty;
        }

        // Return whether the colony has no live cells
        public bool IsEmpty
        {
            get
            {
                for (var y = 0; y < Height; y++)
                {
                    for (var x = 0; x < Width; x++)
                    {
                        if (board[x + y * Width])
                        {
                            return false;
                        }
                    }
                }
                return true;
            }
        }

        // Moves the board to the next state based on Conway's rules.
        private void UpdateBoard()
        {
            // A temp variable to hold the next state while it's being calculated.
            Span newBoard = new bool[Width * Height];

            for (var y = 0; y < Height; y++)
            {
                for (var x = 0; x < Width; x++)
                {
                    var n = CountLiveNeighbors(x, y);
                    var c = board[x + y * Width];

                    // A live cell dies unless it has exactly 2 or 3 live neighbors.
                    // A dead cell remains dead unless it has exactly 3 live neighbors.
                    newBoard[x + y * Width] = c && (n == 2 || n == 3) || !c && n == 3;
                }
            }

            // Set the board to its new state.
            board = newBoard.ToArray();
        }

        // Returns the number of live neighbors around the cell at position (x,y).
        private int CountLiveNeighbors(int x, int y)
        {
            // The number of live neighbors.
            int value = 0;

            // This nested loop enumerates the 9 cells in the specified cells neighborhood.
            for (var j = -1; j <= 1; j++)
            {
                // If y+j is off the board, continue.
                if (y + j < 0 || y + j >= Height)
                {
                    continue;
                }

                for (var i = -1; i <= 1; i++)
                {
                    // If x+i is off the board, continue.
                    if (x + i < 0 || x + i >= Width)
                    {
                        continue;
                    }

                    // Count the neighbor cell at (h,k) if it is alive.
                    value += board[x + i + (y + j) * Width] ? 1 : 0;
                }
            }

            // Subtract 1 if (x,y) is alive since we counted it as a neighbor.
            return value - (board[x + y * Width] ? 1 : 0);
        }

        static void Main(string[] args)
        {
            const int MaxGames = 250;
            const int MaxIterations = 10000;

            DateTime start = DateTime.Now;
            int survived = 0, extinguished = 0;
            for (int i = 0; i < MaxGames; i++)
            {
                Console.WriteLine(i);
                var board = new GameOfLifeBoard(i);
                if (board.Run(MaxIterations))
                {
                    survived++;
                }
                else
                {
                    extinguished++;
                }
            }
            DateTime end = DateTime.Now;

            Console.WriteLine($"Survived:     {survived,6:d}");
            Console.WriteLine($"Extinguished: {extinguished,6:d}");
            Console.WriteLine($"Total time:   {(int)(end - start).TotalSeconds,6:d}");
        }
    }
}

Adiós a MSDN Magazine

“… When the light dies down and you blood runs cold
Then you know what you fear most is growing old…”
Kansas, “End of the Age” (1983)

Sorpresa y tristeza me embargaron a la vez al leer el editorial del que será el último ejemplar de MSDN Magazine. Sorpresa, porque no lo esperaba, y tristeza, casi tanta como la que sentí cuando publicamos el último ejemplar de dotNetManía a mediados de 2013. He leído MSDN Magazine (y sus predecesores) prácticamente desde que tengo «uso de razón» profesional. Y aunque está claro desde hace mucho que la prensa escrita tiene sus días contados, pensaba que los deep pockets de Microsoft nos permitirían seguir disfrutando de la revista aún por muchos años. Pero en los tiempos que corren la Inteligencia Artificial se aplica con ubicuidad, y particularmente a la hora de recortar al máximo los gastos superfluos o que no producen una ganancia evidente o inmediata.

Solo espero y deseo que Microsoft continúe publicando online artículos de la calidad de los que, por ejemplo, se incluyen en este último ejemplar.


Referencia musical: «Drastic Measures» (1983) es muy probablemente el peor álbum de toda la discografía de mi grupo favorito, Kansas. Tan malo, que su no aceptación provocó la desintegración del grupo (para luego reaparecer al cabo de unos años, con miembros y estilo algo diferentes). No obstante, le tengo cierto cariño a ese disco por la cantidad de años que tuve que esperar para oírlo. Es de 1983, una época en que el vinilo (parecía que) estaba muriendo y el CD no se había impuesto todavía. Solo después de llegado el nuevo milenio (aunque años antes de que el streaming comenzara a ser una alternativa) se decidió Sony Music a reeditar el disco en CD. «End of the Age«, a pesar del tono pesimista y las veladas referencias religiosas, es de lo poco del disco que puede tener algún valor (histórico al menos) para un fan de la banda.

Despedida como MVP

«Es de bien nacidos ser agradecidos»
(Refrán que me enseñó mi abuela Guillermina)

Este post está listo desde hace algunos días, pero decidí posponer su publicación hasta que salieran de la lista de los más leídos los posts de júbilo de mis compañeros que muy merecidamente han sido renovados por un año más o elegidos como MVP por primera vez, para no restarles protagonismo.

En mi caso, dejé de pertenecer al programa MVP el pasado 1 de enero. Y lo primero que debo dejar claro es que la decisión fue 100% correcta. He estado casi todo el año fuera de España, sin participar en las actividades de la comunidad. Por otra parte, he estado envuelto en un proyecto apasionante, aprendiendo un montón de cosas nuevas, y lo poco que podía haber hecho para haber merecido tal vez una renovación como MVP, simplemente quedó relegado a un plano secundario. Me refiero, por ejemplo, a cosas como publicar una versión actualizada a C# 4.0 del libro “C# 3.0 y LINQ”, o haber ayudado a mi buen amigo Unai en su excelente actualización del libro de Entity Framework a la versión 4.0 (que ya tengo por aquí; pienso escribir sobre él en los próximos días, tan pronto lo termine de leer). Así que, totalmente, Mea culpa.

Más allá de las florituras, el objetivo de este post es uno solo: expresar mi agradecimiento infinito a Microsoft por haberse fijado en mí por allá por 2004 y hacerme miembro del programa. La gran mayoría de las cosas buenas que han ocurrido en mi vida profesional a partir de entonces tienen que ver, de forma u otra, con esa decisión. En específico, quiero dar las gracias a los dos MVP Lead con los que tuve el honor de interactuar, Cristina González Herrero y Alberto Amescua Bernier, dos personas y profesionales “como la copa de un pino” que se desvivieron siempre a diario por transmitir nuestras quejas, opiniones y sugerencias a los equipos de producto, ponernos en contacto con las personas adecuadas dentro de esa gran empresa, y hacer otros miles de cosas más para que nuestra experiencia de MVP fuera lo más completa posible. Desde aquí les mando un fuerte abrazo, así como a todos los colegas MVP y empleados de Microsoft a los que he tenido el placer de conocer durante este hermoso viaje. Un afectuoso saludo también para los recién elegidos; me agrada sobremanera ver que el programa se rejuvenece y los “mayores” (como yo :-)) van cediendo el protagonismo a las caras jóvenes. Se me antoja que nadie como los cubanos debemos tener clara la importancia de la alternancia y la renovación para la vitalidad de cualquier proyecto.

¡Seguimos en contacto!

NOTA: Las opiniones reflejadas en este artículo son mías propias, y no han sido revisadas ni aprobadas por la empresa en la que trabajo.


Referencia literaria: “Mea culpa” es un libro muy recomendable de uno de mis escritores favoritos, Guillermo Cabrera Infante. Nadie ha sabido captar como él el embrujo de mi Habana natal, esa Habana que persigue en los sueños a los habaneros por muchas décadas que hayan transcurrido desde que te escapaste (¿intentaste escapar?) de allí. Y ya que hablamos sobre Premios Nobel de Literatura, no se pierda el reciente discurso de aceptación del premio de otro gigante de las letras ibero-americanas, Mario Vargas Llosa, disponible aquí.