2012-01-18 1 views
1

J'ai un problème de performance très étrange, lié à l'accès à la mémoire. L'extrait de code est:pourquoi l'accès à la mémoire est si lent?

#include <vector> 
using namespace std; 

vector<int> arrx(M,-1); 
vector< vector<int> > arr(N,arrx); 

... 
for(i=0;i<N;i++){ 
    for(j=0;j<M;j++){ 
    //>>>>>>>>>>>> Part 1 <<<<<<<<<<<<<< 
    // Simple arithmetic operations 
    int n1 = 1 + 2; // does not matter what (actually more complicated) 
    // Integer assignment, without access to array 
    int n2 = n1; 

    //>>>>>>>>>>>> Part 2 <<<<<<<<<<<<<< 
    // This turns out to be most expensive part 
    arr[i][j] = n1; 
    } 
} 

N et M - sont des constantes de l'ordre de 1000 à 10000 ou plus. Lorsque je compile ce code (version finale), il faut environ 15 horloges pour le terminer si la partie 2 est commentée. Avec cette partie le temps d'exécution va jusqu'à 100+ horloges, donc presque 10 fois plus lent. Je m'attendais à ce que l'opération d'assignation soit beaucoup moins coûteuse que même des opérations arithmétiques simples. C'est en fait vrai si nous n'utilisons pas de tableaux. Mais avec ce tableau, l'affectation semble être beaucoup plus coûteuse. J'ai également essayé un tableau 1D au lieu de 2D - le même résultat (pour 2D est évidemment plus lent). J'ai également utilisé int ** ou int * à la place du vecteur < vecteur < int>> ou vecteur < int> - encore une fois le résultat est le même.

Pourquoi est-ce que j'obtiens des performances aussi médiocres dans l'assignation de réseau et puis-je le réparer?

Edit: Encore une observation: dans la partie 2 du code donné si l'on change l'affectation de

arr[i][j] = n1; // 172 clocks 

à

n1 = arr[i][j]; // 16 clocks 

la vitesse (nombre dans les commentaires) monte. Plus intéressant encore, si l'on change la ligne:

arr[i][j] = n1; // 172 clocks 

à

arr[i][j] = arr[i][j] * arr[i][j]; // 110 clocks 

vitesse est également plus élevé que pour une simple affectation est-il une différence entre la lecture et l'écriture de/à la mémoire? Pourquoi ai-je une performance aussi étrange?

Merci d'avance!

+0

C'est plus qu'une simple affectation ... – AJG85

+1

Vous pourriez obtenir de meilleurs résultats en utilisant un tableau à plat au lieu d'un 'vector' imbriqué. Essayez 'boost :: multiarray'. – pmr

+0

et vous pourriez vouloir traiter les cellules dans l'ordre de la mémoire (une meilleure utilisation du cache). – ysdx

Répondre

5

À moins que votre «partie 1» actuelle ne soit significativement plus complexe que votre exemple nous laisse croire, il n'y a pas de surprise ici - l'accès mémoire est lent par rapport à l'arithmétique de base. En outre, si vous compilez avec des optimisations, la plupart ou la totalité de la "partie 1" peut être optimisée car les résultats ne sont jamais utilisés.

+0

la partie 1 peut être pas si complexe, mais la boucle elle-même est plus compliquée que ce que j'ai montré dans l'extrait. Il implique en fait plusieurs opérations en virgule flottante, il doit donc être assez cher dans mes attentes. Pourtant, il s'avère que l'accès à la mémoire est encore plus compliqué – user938720

+1

@ user938720: Qu'est-ce qui vous fait penser que le virgule flottante est cher? –

6

Vos hypothèses sont vraiment mal ...

  1. écriture à la mémoire principale est beaucoup plus lent que de faire une simple addition.
  2. Si vous n'effectuez pas l'écriture, il est probable que la boucle sera entièrement optimisée pour .
2

Vous avez découvert une triste vérité sur les ordinateurs modernes: accès à la mémoire est vraiment lent par rapport à l'arithmétique. Il n'y a rien que tu puisses faire. C'est parce que les champs électriques dans un fil de cuivre voyagent seulement à environ deux tiers de la vitesse de la lumière.

+1

La chaleur est la raison pour laquelle l'horloge * CPU * a cessé d'être plus rapide, mais la vitesse de la lumière est la raison pour laquelle l'horloge * du bus mémoire * a cessé de fonctionner plus vite. – zwol

+0

J'ai fait le calcul, pour une recherche de 50ns, c'est environ 3,75 mètres de fil. Je suppose que je pourrais croire que nous sommes limités par la vitesse de la lumière. Je n'avais pas réalisé que nous étions si proches. C'est fou_. –

2

Vos tableaux imbriqués vont se situer entre 50 et 500 Mo de mémoire. Il faut du temps pour écrire autant de mémoire, et aucune mise en cache intelligente de la mémoire matérielle ne va aider autant.De plus, même une seule écriture en mémoire prendra du temps car elle doit se frayer un chemin à travers des fils de cuivre sur une carte de circuit jusqu'à un morceau de silicium à une certaine distance; la physique gagne. Mais si vous voulez creuser plus, essayez l'outil cachegrind (en supposant qu'il est présent sur votre plate-forme). Sachez simplement que le code que vous utilisez ci-dessus ne permet pas vraiment beaucoup d'optimisation; son modèle d'accès aux données n'a pas un énorme potentiel de réutilisation.

2

Faisons une estimation simple. Une vitesse d'horloge typique du CPU est actuellement de l'ordre de 1 à 2 GHz (giga = 10 pour alimenter neuf). Simplifiant une (vraiment) bonne affaire, cela signifie qu'une opération d'un seul processeur prend environ 1ns (nano = 10 pour alimenter le neuf négatif). Une simple arithmétique comme int additionne plusieurs cycles CPU, d'ordre dix. Maintenant, mémoire: le temps d'accès à la mémoire typique est d'environ 50ns (encore une fois, il n'est pas nécessaire de passer maintenant aux détails sanglants, qui sont nombreux).

Vous voyez que même dans le scénario meilleur des cas, la mémoire est plus lent que CPU par un facteur d'environ 5 à 10.

En fait, je suis balayer sous le tapis une énorme quantité de détails, mais j'espère que l'idée de base est claire. Si vous êtes intéressé, il y a des livres autour (mots-clés: cache, miss cache, localité de données, etc.). This one est daté, mais toujours très bon aux concepts généraux expliquant.

Questions connexes