De Span a Memory

“…Memoria, memoria
(no, I don’t have a gun)…”
Nirvana, “Come as You Are” (1992)

En nuestra entrada anterior presentamos la clase genérica Span<T>, que fue introducida oficialmente con C# 7.2, seguramente por el hecho de aprovecha algunas novedades relativamente menores incorporadas por primera vez al lenguaje en las distintas actualizaciones de la versión 7 (en particular, los valores de retorno por referencia – ref returns, de los que hemos hablado aquí, y las nuevas posibilidades que ofrece stackalloc, que pueden consultarse en esta página de JetBrains). Span<T> y su homólogo de solo lectura ReadOnlySpan<T> nos permiten hacer referencia de una manera uniforme a cualquier zona de memoria contigua de datos homogéneos, independientemente de dónde esa memoria está situada y de cómo ha sido obtenida.

La vez anterior no mencionamos que Span<T> tiene un pequeño inconveniente: a causa de su implementación (el lector interesado encontrará información detallada en los documentos a los que ya hemos hecho referencia), las instancias de Span<T>, si bien pueden apuntar a cualquier zona arbitraria de memoria, ellas mismas sólo pueden residir en la pila (stack) en forma de variables locales o parámetros de entrada de funciones (siempre que éstas no sean asíncronas, como veremos más adelante). Por ejemplo, si se trata de compilar la siguiente clase:

using System;
public class ThisWontCompile
{
    private Span<int> _span;
}

se obtendrá el error “Field or auto-implemented property cannot be of type ‘Span<int>’ unless it is an instance member of a ref struct (CS 8345)“.

Aquí es donde entra a jugar su papel la clase Memory<T>, cuyas instancias sí pueden residir en el heap. Memory<T> no implementa una API tan amplia como Span<T>, pero sí ofrece una propiedad Span que devuelve un Span<T> asociado a la misma zona de memoria.

Un caso que podría presentársele con cierta frecuencia y en el que deberá cambiar Span<T> por Memory<T> es al crear métodos asíncronos que reciban un span. Recuerde que al “desenrollar” un método asíncrono el compilador genera una clase para capturar el estado local y los parámetros de entrada (el mecanismo se describe muy bien aquí). En el caso que nos ocupa, la clase generada tendría que tener un campo de tipo Span<T>, que como hemos visto antes es imposible.

Suponga que quisiéramos hacer asíncrono el método QuickSort de nuestra entrada anterior. Con tan solo añadir al método el modificador async obtendríamos el error “Parameters or locals of type ‘Span<int>’ cannot be declared in async methods or lambda expressions (CS 4012)“:

  public static async void QuickSort<T>(Span<T> span) where T: IComparable

Podríamos resolver el problema así:

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

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

              i++; j--;
          }
      }

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

Este es un caso relativamente bastante sencillo, pero en general (como hemos dicho ya alguna vez aquí) siempre que se trabaja directamente con la memoria se debe ser sumamente cuidadoso. Si piensa utilizar estas clases en contextos más avanzados, asegúrese de leer antes las recomendaciones oficiales de Microsoft sobre el uso de Memory<T> y Span<T>.


Referencia musical: A principios de los ’90 dejé de oír rock (tuve que concentrarme seriamente en buscarme la vida ), así que no me enteré de quién era Nirvana hasta más de una década después, cuando hice mi primer viaje a Seattle y Microsoft nos llevó a visitar el excelente Museum of Pop Culture, que exhibe numerosos objetos memorables relacionados con el grupo. “Come as You Are” fue el segundo single del disco “Nevermind“, que los lanzó a la fama. Puedo dar fe de que el tema se sigue escuchando aún con cierta frecuencia en las emisoras de radio de rock clásico.

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 *