Utilisation pratique du mot-clé `stackalloc`

134

Quelqu'un a-t-il déjà utilisé stackalloclors de la programmation en C #? Je suis conscient de ce que fait, mais le seul moment où il apparaît dans mon code est par accident, car Intellisense le suggère lorsque je commence à taper static, par exemple.

Bien que cela ne soit pas lié aux scénarios d'utilisation de stackalloc, je fais en fait une quantité considérable d'interopérabilité héritée dans mes applications, donc de temps en temps, je pourrais recourir à l'utilisation de unsafecode. Mais néanmoins, je trouve généralement des moyens d'éviter unsafecomplètement.

Et comme la taille de la pile pour un seul thread dans .Net est de ~ 1 Mo (corrigez-moi si je me trompe), je suis encore plus réservé stackalloc.

Y a-t-il des cas pratiques où l'on pourrait dire: "c'est exactement la bonne quantité de données et de traitement pour que je devienne dangereux et que je les utilise stackalloc"?

Groo
la source
5
juste remarqué qu'il l' System.Numbersutilise beaucoup referencesource.microsoft.com/#mscorlib/system/...
Slai

Réponses:

150

La seule raison à utiliser stackallocest la performance (que ce soit pour les calculs ou l'interopérabilité). En utilisant stackallocau lieu d'un tableau alloué de tas, vous créez moins de pression GC (le GC doit moins fonctionner), vous n'avez pas besoin d'épingler les tableaux, il est plus rapide à allouer qu'un tableau de tas, et il est automatiquement libéré sur la méthode exit (les tableaux alloués au tas ne sont désalloués que lorsque GC s'exécute). De plus, en utilisant à la stackallocplace d'un allocateur natif (comme malloc ou l'équivalent .Net), vous gagnez également en vitesse et en désallocation automatique à la sortie de la portée.

En termes de performances, si vous utilisez, stackallocvous augmentez considérablement les chances de succès du cache sur le processeur en raison de la localité des données.

Pop Catalin
la source
26
Localité des données, bon point! C'est ce que la mémoire gérée accomplira rarement lorsque vous souhaitez allouer plusieurs structures ou tableaux. Merci!
Groo
22
Les allocations de tas sont généralement plus rapides pour les objets gérés que pour les objets non gérés car il n'y a pas de liste libre à parcourir; le CLR incrémente simplement le pointeur de tas. En ce qui concerne la localité, les allocations séquentielles sont plus susceptibles de finir colocalisées pour les processus gérés de longue durée en raison du compactage du tas.
Shea
1
"il est plus rapide à allouer qu'un tableau de tas" Pourquoi? Juste une localité? Quoi qu'il en soit, c'est juste une bosse de pointeur, non?
Max Barraclough
2
@MaxBarraclough Parce que vous ajoutez le coût GC aux allocations de tas sur la durée de vie de l'application. Coût d'allocation total = allocation + désallocation, dans ce cas pointeur bump + GC Heap, vs pointeur bump + pointeur décrémentation Stack
Pop Catalin
35

J'ai utilisé stackalloc pour allouer des tampons pour le travail DSP en [quasi] temps réel. C'était un cas très particulier où les performances devaient être aussi cohérentes que possible. Notez qu'il y a une différence entre la cohérence et le débit global - dans ce cas, je n'étais pas préoccupé par le fait que les allocations de tas soient trop lentes, juste par le non déterminisme du ramasse-miettes à ce stade du programme. Je ne l'utiliserais pas dans 99% des cas.

Jim Arnold
la source
25

stackallocn'est pertinent que pour le code non sécurisé. Pour le code géré, vous ne pouvez pas décider où allouer les données. Les types de valeur sont alloués sur la pile par défaut (sauf s'ils font partie d'un type de référence, auquel cas ils sont alloués sur le tas). Les types de référence sont alloués sur le tas.

La taille de pile par défaut pour une application .NET standard est de 1 Mo, mais vous pouvez la modifier dans l'en-tête PE. Si vous démarrez explicitement les threads, vous pouvez également définir une taille différente via la surcharge du constructeur. Pour les applications ASP.NET, la taille de pile par défaut n'est que de 256 Ko, ce qu'il faut garder à l'esprit si vous basculez entre les deux environnements.

Brian Rasmussen
la source
Est-il possible de modifier la taille de pile par défaut à partir de Visual Studio?
configurateur
@configurator: Pas pour autant que je sache.
Brian Rasmussen
17

Initialisation Stackalloc des travées. Dans les versions précédentes de C #, le résultat de stackalloc ne pouvait être stocké que dans une variable locale de pointeur. Depuis C # 7.2, stackalloc peut désormais être utilisé dans le cadre d'une expression et peut cibler un span, et cela peut être fait sans utiliser le mot clé unsafe. Ainsi, au lieu d'écrire

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

Vous pouvez écrire simplement:

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

Ceci est également extrêmement utile dans les situations où vous avez besoin d'un peu d'espace de travail pour effectuer une opération, mais que vous voulez éviter d'allouer de la mémoire de tas pour des tailles relativement petites

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

Source: C # - Tout sur Span: Exploration d'un nouveau Mainstay .NET

anth
la source
4
Merci pour le conseil. Il semble que chaque nouvelle version de C # se rapproche un peu de C ++, ce qui est en fait une bonne chose à mon humble avis.
Groo
1
Comme on peut le voir ici et ici , Spann'est hélas pas disponible dans .NET Framework 4.7.2 et même pas dans 4.8 ... Donc cette nouvelle fonctionnalité de langage est encore d'une utilité limitée pour le moment.
Frédéric
2

Il y a de bonnes réponses à cette question, mais je veux juste souligner que

Stackalloc peut également être utilisé pour appeler des API natives

De nombreuses fonctions natives nécessitent que l'appelant alloue un tampon pour obtenir le résultat renvoyé. Par exemple, la fonction CfGetPlaceholderInfo dans cfapi.ha la signature suivante.

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

Pour l'appeler en C # via l'interopérabilité,

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

Vous pouvez utiliser stackalloc.

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