web-dev-qa-db-fra.com

Quelle est la différence entre la mémoire partagée CUDA et la mémoire globale?

Je suis confus quant à la façon d'utiliser la mémoire partagée et globale dans CUDA, en particulier en ce qui concerne les éléments suivants:

  • Lorsque nous utilisons cudaMalloc(), obtenons-nous un pointeur vers la mémoire partagée ou globale?
  • La mémoire globale réside-t-elle sur l'hôte ou le périphérique?
  • Y a-t-il une limite de taille pour l'un ou l'autre?
  • Quel est le plus rapide d'accès?
  • Le stockage d'une variable dans la mémoire partagée équivaut-il à transmettre son adresse via le noyau? C'est à dire. au lieu d'avoir

    __global__ void kernel() {
       __shared__ int i;
       foo(i);
    }
    

    pourquoi ne pas faire de façon équivalente

    __global__ void kernel(int *i_ptr) {
       foo(*i_ptr);
    }
    
    int main() {
       int *i_ptr;
       cudaMalloc(&i_ptr, sizeof(int));
       kernel<<<blocks,threads>>>(i_ptr);
    }
    

Il y a eu de nombreuses questions sur des problèmes de vitesse spécifiques dans la mémoire globale par rapport à la mémoire partagée, mais aucun ne contenait un aperçu du moment où utiliser l'un ou l'autre dans la pratique.

Merci beaucoup

36
mchen
  • Lorsque nous utilisons cudaMalloc ()

    Afin de stocker des données sur le GPU qui peuvent être communiquées à l'hôte, nous devons avoir une mémoire allouée qui vit jusqu'à ce qu'elle soit libérée, voir la mémoire globale comme l'espace de mémoire avec la vie jusqu'à la fermeture ou la libération de l'application, elle est visible vers n'importe quel thread et bloc ayant un pointeur vers cette région de mémoire. La mémoire partagée peut être considérée comme un espace de pile avec de la vie jusqu'à ce qu'un bloc d'un noyau se termine, la visibilité est limitée aux seuls threads dans le même bloc. Ainsi, cudaMalloc est utilisé pour allouer de l'espace dans la mémoire globale.

  • Avons-nous un pointeur vers la mémoire partagée ou globale?

    Vous obtiendrez un pointeur vers une adresse mémoire résidant dans la mémoire globale.

  • La mémoire globale réside-t-elle sur l'hôte ou le périphérique?

    La mémoire globale réside sur l'appareil. Cependant, il existe des moyens d'utiliser la mémoire hôte en tant que mémoire "globale" en utilisant la mémoire mappée, voir: Considérations sur la mémoire CUDA Zero Copy cependant, il peut s'agir de vitesses lentes en raison des limitations de vitesse de transfert de bus.

  • Y a-t-il une limite de taille pour l'un ou l'autre?

    La taille de la mémoire globale dépend d'une carte à l'autre, de zéro à 32 Go (V100). Alors que la mémoire partagée dépend de la capacité de calcul. Tout ce qui est inférieur à la capacité de calcul 2.x a un maximum de 16 Ko de mémoire partagée par multiprocesseur (où la quantité de multiprocesseurs varie d'une carte à l'autre). Et les cartes avec une capacité de calcul de 2.x et plus ont un minimum de 48 Ko de mémoire partagée par multiprocesseur.

    Voir https://en.wikipedia.org/wiki/CUDA#Version_features_and_specifications

    Si vous utilisez de la mémoire mappée, la seule limitation est la quantité de mémoire de la machine hôte.

  • Quel est le plus rapide d'accès?

    En termes de nombres bruts, la mémoire partagée est beaucoup plus rapide (mémoire partagée ~ 1,7 To/s, tandis que la mémoire globale ~ XXX Go/s). Cependant, pour faire tout ce dont vous avez besoin pour remplir la mémoire partagée avec quelque chose, vous tirez généralement de la mémoire globale. Si l'accès mémoire à la mémoire globale est fusionné (non aléatoire) et de grande taille Word, vous pouvez atteindre des vitesses proches de la limite théorique de centaines de Go/s selon la carte et son interface mémoire.

    L'utilisation de la mémoire partagée est lorsque vous devez, dans un bloc de threads, réutiliser des données déjà extraites ou évaluées de la mémoire globale. Ainsi, au lieu de tirer à nouveau de la mémoire globale, vous le placez dans la mémoire partagée pour que d'autres threads du même bloc puissent le voir et le réutiliser.

    Il est également courant d'être utilisé comme bloc-notes afin de réduire la pression de registre affectant le nombre de groupes de travail pouvant être exécutés en même temps.

  • Le stockage d'une variable dans la mémoire partagée équivaut-il à transmettre son adresse via le noyau?

    Non, si vous passez une adresse de quelque chose, c'est toujours une adresse à la mémoire globale. À partir de l'hôte, vous ne pouvez pas définir la mémoire partagée, sauf si vous la transmettez soit comme une constante où le noyau définit la mémoire partagée à cette constante, soit vous lui passez une adresse dans la mémoire globale où elle est extraite par le noyau lorsque cela est nécessaire.

48
1-----1

Le contenu de la mémoire globale est visible par tous les threads de la grille. Tout thread peut lire et écrire dans n'importe quel emplacement de la mémoire globale.

La mémoire partagée est distincte pour chaque bloc de la grille. Tout thread d'un bloc peut lire et écrire dans la mémoire partagée de ce bloc. Un thread d'un bloc ne peut pas accéder à la mémoire partagée d'un autre bloc.

  1. cudaMalloc alloue toujours la mémoire globale.
  2. La mémoire globale réside sur l'appareil.
  3. De toute évidence, chaque mémoire a une taille limite. La mémoire globale est la quantité totale de DRAM du GPU que vous utilisez. par exemple, j'utilise GTX460M qui a 1536 Mo de DRAM, donc 1536 Mo de mémoire globale. La mémoire partagée est spécifiée par l'architecture du périphérique et est mesurée par bloc. Les appareils de capacité de calcul 1.0 à 1.3 ont 16 KB/Block, les versions 2.0 et ultérieures ont 48 KB/Block mémoire partagée par défaut.
  4. La mémoire partagée est beaucoup plus rapide d'accès que la mémoire globale. C'est comme un cache local partagé entre les threads d'un bloc.
  5. Non. Seules les adresses de mémoire globale peuvent être transmises à un noyau lancé depuis l'hôte. Dans votre premier exemple, la variable est lue dans la mémoire partagée, tandis que dans le second, elle est lue dans la mémoire globale.

Mise à jour:

Les périphériques de capacité de calcul 7.0 (architecture Volta) permettent d'allouer jusqu'à 96 Ko de mémoire partagée par bloc, à condition que les conditions suivantes soient remplies.

  • La mémoire partagée est allouée dynamiquement
  • Avant de lancer le noyau, la taille maximale de la mémoire partagée dynamique est spécifiée à l'aide de la fonction cudaFuncSetAttribute comme suit.

__global__ void MyKernel(...)
{
    extern __shared__ float shMem[];
}

int bytes = 98304; //96 KB
cudaFuncSetAttribute(MyKernel, cudaFuncAttributeMaxDynamicSharedMemorySize, bytes);

MyKernel<<<gridSize, blockSize, bytes>>>(...);
10
sgarizvi

La mémoire partagée CUDA est une mémoire partagée entre les threads d'un bloc, c'est-à-dire qu'entre les blocs d'une grille, le contenu de la mémoire partagée n'est pas défini. Il peut être considéré comme un cache L2 géré manuellement.

La mémoire globale réside généralement sur l'appareil, mais les versions récentes de CUDA (si l'appareil le prend en charge) peuvent mapper la mémoire de l'hôte dans l'espace d'adressage de l'appareil, déclenchant un transfert in situ DMA transfert de l'hôte à la mémoire de l'appareil) dans de telles occasions.

Il y a une limite de taille sur la mémoire partagée, selon l'appareil. Son rapporté dans les capacités de l'appareil, récupéré lors de l'énumération des appareils CUDA. La mémoire globale est limitée par la mémoire totale disponible pour le GPU. Par exemple, un GTX680 offre 48 Ko de mémoire partagée et 2 Go de mémoire d'appareil.

La mémoire partagée est plus rapide d'accès que la mémoire globale, mais les modèles d'accès doivent être soigneusement alignés (pour la mémoire partagée et la mémoire globale) pour être efficaces. Si vous ne pouvez pas aligner correctement vos modèles d'accès, utilisez des textures (également de la mémoire globale, mais accessibles via une circurity et un cache différents, qui peuvent mieux gérer l'accès non aligné).

Le stockage d'une variable dans la mémoire partagée équivaut-il à transmettre son adresse via le noyau?

Non, absolument pas. Le code que vous proposez serait un cas où vous utiliseriez une mémoire globale transférée in situ. La mémoire partagée ne peut pas être transmise entre les noyaux, car le contenu d'un bloc partagé est défini dans un bloc d'exécution de threads uniquement.

3
datenwolf