Generalización del acceso a memoria con Span

“…as the links span our endless caresses
For the freedom of life everlasting…”
Yes, “The Revealing Science of God” (1973)

¡Feliz 2019! Deseo de corazón a todos mis lectores que hayan tenido una buena despedida del año que recién terminó. “Año nuevo, vida nueva”, decía mi abuela Guillermina, y uno de mis propósitos para este año es recuperar la sistematicidad en la escritura, que perdí completamente a finales del año pasado.

Esta primera entrada del año, que llevaba en el tintero unos cuantos meses sin que yo hubiera logrado terminarla, trata sobre el tipo de datos Span<T> (espacio de nombres System.Memory), de relativamente reciente aparición: fue incorporado a .NET Core 2.1. Para utilizarlo en proyectos de .NET Framework, sin embargo, tendrá que importar de Nuget el paquete System.Memory, como puede verse en la captura de pantalla a continuación. Como es muy pronto para empezar a quejarse ya, dejaré las quejas al respecto para el final.

Span<T> es un nuevo tipo por valor (value type) que nos permitirá hacer referencia de una manera uniforme a cualquier zona de memoria contigua de datos homogéneos, independientemente de si esta zona de memoria pertenece a un objeto de código manejado tradicional residente en el heap (T[]), a un buffer nativo (T*) o array creado en la pila mediante stackalloc, o incluso una estructura de datos a la que hayamos obtenido una referencia IntPtr mediante la interacción con código no manejado. A partir de cualquiera de esas fuentes se puede crear una variable de tipo Span<T> (o su pariente de solo lectura ReadOnlySpan<T>). Aparte del hecho de que Span<T> garantiza un acceso con validación de rango a cualquiera de los elementos de la estructura a través de un índice, como si de un array se tratara (s[i]), este tipo de datos ofrece otra ventaja que es muy importante de cara al rendimiento: no reserva buffers intermedios y evita las copias de datos innecesarias. Deja vu! Empezamos el nuevo año como mismo terminamos el viejo: hablando de un tipo de datos por valor (estructura) que ofrece mejoras de rendimiento.

El siguiente ejemplo muestra cómo crear un Span<T> a partir de un array, cómo hacer referencia a un elemento cualquiera, y (lo mejor de todo), cómo utilizar el método Slice para denotar a un subconjunto (“rebanada”) de los elementos del span. Para finalizar, ordeno la rebanada mediante una implementación “espantosa”1 de QuickSort. Tengo la impresión de que este algoritmo se logra expresar mucho más elegantemente cuando se utiliza Span<T>.

using System;

namespace SpanDemo
{
    public static class SortingRoutines
    {
        static void Main(string[] args)
        {
            var random = new Random();

            int[] values = new int[100]; 
            for (int i = 0; i < 100; i++)
                values[i] = random.Next(0, 1000);

            var span = new Span<int>(values);
            // Referencias a elemento
            Console.WriteLine("First element: {0}", span[0]);
            span[1]++;
            // "Rebanada" del span original
            var span2 = span.Slice(start: 3, length: 10);

            QuickSort(span2);
            foreach (int i in span2)
                Console.WriteLine(i);
        }

        public static void QuickSort<T>(Span<T> span) where T: IComparable 
        {
            int i = 0, j = span.Length - 1;
            T pivot = span[(span.Length - 1) / 2];

            while (i <= j)
            {
                while (span[i].CompareTo(pivot) < 0) i++;
                while (span[j].CompareTo(pivot) > 0) j--;
                if (i <= j)
                {
                    T tmp = span[i];
                    span[i] = span[j];
                    span[j] = tmp; 

                    i++; j--;
                }
            }

            // Llamadas recursivas
            if (j > 0)
                QuickSort<T>(span.Slice(0, j + 1));
            if (i < span.Length - 1)
                QuickSort<T>(span.Slice(i, span.Length - i));
        }
    }
}

Dejo al lector la investigación de cómo se implementa internamente la estructura Span<T>, algo que describen perfectamente tanto Ahson Khan en Code Magazine como Stephen Toub en MSDN Magazine.

Para finalizar, la diatriba prometida contra la exclusión de Span<T> de .NET Framework. No es la necesidad de añadir un paquete, ni mucho menos; es la fractura que empieza a producirse en el lenguaje por culpa de ello. Por ejemplo, Microsoft señala en este artículo que los futuros índices y rangos de C# 8.0 dependen de Span<T> y otras nuevas características, y no estarán disponibles al desarrollar aplicaciones para .NET Framework. Oficialmente, nos dicen que estos nuevos tipos no se añaden a .NET Framework para no comprometer la estabilidad de la plataforma; en el fondo, lo que pienso es que lo que Microsoft quiere es obligarnos a ir hacia .NET Core. Tal vez algún lector logre aclararme esta confusión.


1 “Espantoso” no viene de espanto, sino de Span<T> .

Referencia musical: El grupo británico Yes (elegido al Salón de la Fama del Rock’n’Roll en 2017) es uno de los máximos exponentes de la corriente que, por razones obvias, se ha dado en llamar rock sinfónico o progresivo, y “Tales from Topographic Oceans” (1973), al que pertenece la referencia musical de hoy, uno de los primeros exponentes de álbum conceptual, en este caso inspirado en la “Autobiografía de un Yogi”, de Paramahansa Yogananda.

Octavio Hernandez

Desarrollador y consultor en tecnologías .NET. Microsoft C# MVP entre 2004 y 2010.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *