Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Importante
Las técnicas descritas en esta sección mejoran el rendimiento cuando se aplican a las rutas de acceso activas en el código. Las rutas de acceso activas son las secciones del código base que se ejecutan a menudo y repetidamente en operaciones normales. La aplicación de estas técnicas al código que no se ejecuta a menudo tendrá un impacto mínimo. Antes de realizar cambios para mejorar el rendimiento, es fundamental medir una línea base. A continuación, analice esa línea de base para determinar dónde se producen los cuellos de botella de memoria. Puede obtener información sobre muchas herramientas multiplataforma para medir el rendimiento de la aplicación en la sección diagnóstico e instrumentación. Puede practicar una sesión de generación de perfiles en el tutorial para medir el uso de memoria en la documentación de Visual Studio.
Una vez que haya medido el uso de memoria y haya determinado que puede reducir las asignaciones, use las técnicas de esta sección para reducir las asignaciones. Después de cada cambio sucesivo, mida de nuevo el uso de memoria. Asegúrese de que cada cambio tiene un impacto positivo en el uso de memoria en la aplicación.
El trabajo de rendimiento en .NET suele significar eliminar las asignaciones de memoria de tu código. Cada bloque de memoria que asigne debe liberarse finalmente. Menos asignaciones reducen el tiempo invertido en la recolección de elementos no utilizados. Facilita un tiempo de ejecución más predecible al eliminar la recolección de elementos no utilizados de trayectorias de código específicas.
Una táctica común para reducir las asignaciones es cambiar las estructuras de datos críticas de tipos de class
a tipos de struct
. Este cambio afecta a la semántica del uso de esos tipos. Los parámetros y los resultados ahora se pasan por valor en lugar de por referencia. El costo de copiar un valor es insignificante si los tipos son pequeños, tres palabras o menos (teniendo en cuenta que una palabra es de tamaño natural de un entero). Es medible y puede tener un impacto real en el rendimiento de los tipos más grandes. Para combatir el efecto de la copia, los desarrolladores pueden pasar estos tipos mediante ref
para obtener la semántica prevista.
Las características de C# ref
ofrecen la capacidad de expresar la semántica deseada para struct
los tipos sin afectar negativamente a su facilidad de uso general. Antes de estas mejoras, los desarrolladores necesitaban recurrir a construcciones unsafe
con punteros y memoria sin procesar para lograr el mismo impacto en el rendimiento. El compilador genera código seguro verificable para las nuevas ref
características relacionadas.
El código seguro verificable significa que el compilador detecta posibles saturaciones de búfer o acceso a memoria sin asignar o libre. El compilador detecta y evita algunos errores.
Pasar y devolver por referencia
Las variables en C# almacenan valores. En struct
los tipos, el valor es el contenido de una instancia del tipo . En class
los tipos, el valor es una referencia a un bloque de memoria que almacena una instancia del tipo . Agregar el ref
modificador significa que la variable almacena la referencia al valor. En los tipos struct
, la referencia apunta al almacenamiento que contiene el valor. En los tipos class
, la referencia apunta al almacenamiento que contiene la referencia al bloque de memoria.
En C#, los parámetros de los métodos se pasan por valor y los valores de retorno se devuelven por valor. El valor del argumento se pasa al método . El valor del argumento return es el valor devuelto.
El ref
, in
, ref readonly
, o out
modificador indica que el argumento se pasa por referencia. Se pasa al método una referencia a la ubicación de almacenamiento. Agregar ref
a la firma del método significa que el valor devuelto se devuelve por referencia. Una referencia a la ubicación de almacenamiento es el valor devuelto.
También puede usar la asignación de referencias para que una variable haga referencia a otra variable. Una asignación típica copia el valor del lado derecho en la variable del lado izquierdo de la asignación. Una asignación de referencia copia la ubicación de memoria de la variable en el lado derecho a la variable del lado izquierdo. Ahora ref
hace referencia a la variable original:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment
Console.WriteLine(location); // output: 42
sameLocation = 19; // assignment
Console.WriteLine(anInteger); // output: 19
Al asignar una variable, se cambia su valor. Cuando se asigna por referencia una variable, se cambia aquello a lo que hace referencia.
Puede trabajar directamente con el almacenamiento para los valores mediante variables ref
, pasar por referencia y asignar por referencia. Las reglas de ámbito aplicadas por el compilador garantizan la seguridad al trabajar directamente con el almacenamiento.
Los modificadores ref readonly
y in
ambos indican que el argumento debe pasarse por referencia y no se puede reasignar en el método. La diferencia es que ref readonly
indica que el método usa el parámetro como una variable. El método puede capturar el parámetro o puede devolver el parámetro por referencia de solo lectura. En esos casos, debe usar el modificador ref readonly
. De lo contrario, el in
modificador ofrece más flexibilidad. No es necesario agregar el in
modificador a un argumento para un in
parámetro, por lo que puede actualizar las firmas de API existentes de forma segura mediante el in
modificador . El compilador emite una advertencia si no agrega los modificadores ref
o in
a un argumento para un parámetro ref readonly
.
Contexto seguro para referencia
C# incluye reglas para ref
expresiones para asegurarse de que no se puede tener acceso a una ref
expresión donde el almacenamiento al que hace referencia ya no es válido. Considere el ejemplo siguiente:
public ref int CantEscape()
{
int index = 42;
return ref index; // Error: index's ref safe context is the body of CantEscape
}
El compilador notifica un error porque no se puede devolver una referencia a una variable local desde un método . El autor de la llamada no puede acceder al almacenamiento al que se hace referencia. El contexto seguro ref define el ámbito en el que una ref
expresión es segura para acceder o modificar. En la tabla siguiente se enumeran los contextos seguros de referencia para los tipos de variable.
ref
los campos no se pueden declarar en un class
ni en un struct
sin referencia, por lo que esas filas no están en la tabla:
Declaración | ref contexto seguro |
---|---|
local no por referencia | bloque donde se declara una local |
parámetro no por referencia | método actual |
ref , ref readonly , in parámetros |
método de llamada |
Parámetro out |
método actual |
Campo class |
método de llamada |
campo sin referencia struct |
método actual |
Campo ref de ref struct |
método de llamada |
Se puede devolver ref
una variable si su contexto seguro para referencia es el método que realiza la llamada. Si su contexto seguro para referencia es el método actual o un bloque, no se permite la devolución ref
. En el fragmento de código siguiente se muestran dos ejemplos. Se puede acceder a un campo miembro desde el ámbito que invoca un método, por lo que el ámbito seguro de referencia de un campo de clase o estructura es el método que llama. El contexto seguro para referencia para un parámetro con los modificadores ref
o in
es todo el método. Ambos se pueden devolver ref
desde un método miembro.
private int anIndex;
public ref int RetrieveIndexRef()
{
return ref anIndex;
}
public ref int RefMin(ref int left, ref int right)
{
if (left < right)
return ref left;
else
return ref right;
}
Nota:
Cuando el ref readonly
modificador o in
se aplica a un parámetro , ese parámetro se puede devolver mediante ref readonly
, no ref
.
El compilador garantiza que una referencia no pueda escapar su contexto seguro ref. Puede usar ref
parámetros, ref return
y ref
variables locales de forma segura porque el compilador detecta si ha escrito accidentalmente código en el que se puede tener acceso a una ref
expresión cuando su almacenamiento no es válido.
Contexto seguro y estructuras de referencia
ref struct
Los tipos requieren más reglas para asegurarse de que se pueden usar de forma segura. Un ref struct
tipo puede incluir ref
campos. Esto requiere la introducción de un contexto seguro. Para la mayoría de los tipos, el contexto seguro es el método de llamada. Es decir, un valor que no es un ref struct
elemento siempre se puede devolver desde un método .
Informalmente, el contexto seguro de un ref struct
es el ámbito al que se puede acceder a todos sus ref
campos. En otras palabras, es la intersección del contexto seguro para referencia de todos sus campos ref
. El método siguiente devuelve un objeto ReadOnlySpan<char>
a un campo miembro, por lo que su contexto seguro es el método :
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
Por el contrario, el código siguiente emite un error porque el miembro ref field
de Span<int>
se refiere a la matriz de enteros asignada en la pila. No puede aplicar escape al método:
public Span<int> M()
{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}
Unificar tipos de memoria
La introducción de System.Span<T> y System.Memory<T> proporciona un modelo unificado para trabajar con memoria.
System.ReadOnlySpan<T> y System.ReadOnlyMemory<T> proporcionan versiones de solo lectura para acceder a la memoria. Todas proporcionan una abstracción sobre un bloque de memoria que almacena una matriz de elementos similares. La diferencia es que Span<T>
y ReadOnlySpan<T>
son tipos ref struct
, mientras que Memory<T>
y ReadOnlyMemory<T>
son tipos struct
. Los intervalos contienen un ref field
. Por lo tanto, las instancias de un objeto span no pueden dejar su contexto seguro. El contexto seguro de un ref struct
es el contexto ref seguro de su ref field
. La implementación de Memory<T>
y ReadOnlyMemory<T>
elimina esta restricción. Estos tipos se usan para acceder directamente a los búferes de memoria.
Mejorar el rendimiento con seguridad para referencia
El uso de estas características para mejorar el rendimiento implica estas tareas:
-
Evitar asignaciones: Cuando cambias un tipo de
class
a unstruct
, cambias la forma en que se almacena. Las variables locales se almacenan en la pila. Los miembros se almacenan en línea cuando se asigna el objeto contenedor. Este cambio significa menos asignaciones, lo que reduce el trabajo que realiza el recolector de basura. También puede disminuir la presión de memoria para que el recolector de basura se ejecute con menos frecuencia. -
Conservar la semántica de referencia: cambiar un tipo de
class
a astruct
cambia la semántica de pasar una variable a un método. El código que modificó el estado de sus parámetros necesita modificaciones. Ahora que el parámetro es ,struct
el método modifica una copia del objeto original. Puede restaurar la semántica original pasando ese parámetro como parámetroref
. Después de ese cambio, el método modifica de nuevo el originalstruct
. -
Evitar copiar datos: la copia de tipos
struct
más grandes puede afectar al rendimiento en algunas rutas de acceso de código. También puede agregar elref
modificador para pasar estructuras de datos más grandes a métodos por referencia en lugar de por valor. -
Restringir modificaciones: cuando se pasa un
struct
tipo por referencia, el método llamado podría modificar el estado de la estructura. Puede reemplazar el modificadorref
con los modificadoresref readonly
oin
para indicar que el argumento no se puede modificar. Se prefiereref readonly
cuando el método captura el parámetro o lo devuelve por referencia de solo lectura. También puede crear tiposreadonly struct
ostruct
con miembrosreadonly
para proporcionar más control sobre los miembros de unstruct
que se pueden modificar. -
Manipular directamente la memoria: algunos algoritmos son más eficaces al tratar estructuras de datos como un bloque de memoria que contiene una secuencia de elementos. Los tipos
Span
yMemory
proporcionan acceso seguro a bloques de memoria.
Ninguna de estas técnicas requiere unsafe
código. Se usa sabiamente, puede obtener características de rendimiento del código seguro que anteriormente solo era posible mediante técnicas no seguras. Puede probar las técnicas en el tutorial sobre la reducción de las asignaciones de memoria.