Uso práctico de la palabra clave `stackalloc`

134

¿Alguien ha usado alguna vez stackallocmientras programaba en C #? Soy consciente de lo que hace, pero la única vez que aparece en mi código es por accidente, porque Intellisense lo sugiere cuando empiezo a escribir static, por ejemplo.

Aunque no está relacionado con los escenarios de uso de stackalloc, en realidad hago una cantidad considerable de interoperabilidad heredada en mis aplicaciones, por lo que de vez en cuando podría recurrir al uso del unsafecódigo. Pero, sin embargo, generalmente encuentro formas de evitar por unsafecompleto.

Y dado que el tamaño de la pila para un solo hilo en .Net es ~ 1Mb (corrígeme si me equivoco), estoy aún más reservado de usar stackalloc.

¿Hay algunos casos prácticos en los que uno podría decir: "esta es exactamente la cantidad correcta de datos y procesamiento para que no sea seguro y lo use stackalloc"?

Groo
fuente
55
Acabo de notar que System.Numbersutiliza una gran cantidad referencesource.microsoft.com/#mscorlib/system/...
Slai

Respuestas:

150

La única razón para usar stackalloces el rendimiento (ya sea para cálculos o interoperabilidad). Al usar en stackalloclugar de una matriz asignada de almacenamiento dinámico, crea menos presión de GC (la GC necesita ejecutarse menos), no necesita fijar las matrices, es más rápido asignar que una matriz de almacenamiento dinámico, y se libera automáticamente en el método salir (las matrices de montón asignadas solo se desasignan cuando se ejecuta GC). Además, al usar en stackalloclugar de un asignador nativo (como malloc o el equivalente .Net), también gana velocidad y desasignación automática al salir del alcance.

En cuanto al rendimiento, si lo usa stackalloc, aumentará enormemente la posibilidad de que la CPU tenga aciertos en la memoria caché debido a la localidad de los datos.

Pop Catalin
fuente
26
Localidad de datos, buen punto! Eso es lo que rara vez logrará la memoria administrada cuando desee asignar varias estructuras o matrices. ¡Gracias!
Groo
22
Las asignaciones de montón suelen ser más rápidas para los objetos administrados que para los no administrados porque no hay una lista libre para recorrer; el CLR simplemente incrementa el puntero del montón. En cuanto a la localidad, es más probable que las asignaciones secuenciales terminen ubicadas para procesos administrados de larga ejecución debido a la compactación del montón.
Shea
1
"es más rápido de asignar que una matriz de montón" ¿Por qué? ¿Solo localidad? De cualquier manera, es solo un golpe de puntero, ¿no?
Max Barraclough
2
@MaxBarraclough Porque agrega el costo de GC a las asignaciones de almacenamiento dinámico durante la vida útil de la aplicación. Costo total de asignación = asignación + desasignación, en este caso, golpe de puntero + GC Heap, vs golpe de puntero + disminución de puntero Pila
Pop Catalin
35

He usado stackalloc para asignar buffers para el trabajo DSP [casi] en tiempo real. Fue un caso muy específico en el que el rendimiento debía ser lo más consistente posible. Tenga en cuenta que hay una diferencia entre la consistencia y el rendimiento general: en este caso, no me preocupaba que las asignaciones de montón fueran demasiado lentas, solo con el no determinismo de la recolección de basura en ese punto del programa. No lo usaría en el 99% de los casos.

Jim Arnold
fuente
25

stackallocsolo es relevante para el código inseguro. Para el código administrado, no puede decidir dónde asignar los datos. Los tipos de valor se asignan en la pila por defecto (a menos que sean parte de un tipo de referencia, en cuyo caso se asignan en el montón). Los tipos de referencia se asignan en el montón.

El tamaño de pila predeterminado para una aplicación .NET simple vainilla es de 1 MB, pero puede cambiar esto en el encabezado PE. Si está iniciando hilos explícitamente, también puede establecer un tamaño diferente a través de la sobrecarga del constructor. Para las aplicaciones ASP.NET, el tamaño de pila predeterminado es de solo 256K, que es algo a tener en cuenta si cambia entre los dos entornos.

Brian Rasmussen
fuente
¿Es posible cambiar el tamaño de pila predeterminado de Visual Studio?
configurador
@configurator: No que yo sepa.
Brian Rasmussen
17

Stackalloc inicialización de tramos. En versiones anteriores de C #, el resultado de stackalloc solo podía almacenarse en una variable local de puntero. A partir de C # 7.2, stackalloc ahora se puede usar como parte de una expresión y puede apuntar a un intervalo, y eso se puede hacer sin usar la palabra clave insegura. Por lo tanto, en lugar de escribir

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Puedes escribir simplemente:

Span<byte> bytes = stackalloc byte[length];

Esto también es extremadamente útil en situaciones en las que necesita algo de espacio reutilizable para realizar una operación, pero desea evitar asignar memoria de almacenamiento dinámico para tamaños relativamente pequeños

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Fuente: C # - Todo sobre Span: Explorando un nuevo pilar de .NET

anth
fuente
44
Gracias por el consejo. Parece que cada nueva versión de C # se acerca un poco más a C ++, que en realidad es algo bueno en mi humilde opinión.
Groo
1
Como se puede ver aquí y aquí , Spanlamentablemente no está disponible en .NET Framework 4.7.2 e incluso no en 4.8 ... Por lo tanto, la nueva función de lenguaje todavía es de uso limitado por el momento.
Frederic
2

Hay algunas respuestas geniales en esta pregunta, pero solo quiero señalar que

Stackalloc también se puede usar para llamar a API nativas

Muchas funciones nativas requieren que la persona que llama asigne un búfer para obtener el resultado de retorno. Por ejemplo, la función CfGetPlaceholderInfocfapi.h tiene la siguiente firma.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Para llamarlo en C # a través de interoperabilidad,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Puedes hacer uso de stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
fjch1997
fuente