44 votes

Pointeur vs. Variable en C++

Lors d'un entretien d'embauche, on m'a posé la question suivante : "En C++, comment accéder plus rapidement à une variable, par l'identifiant normal de la variable ou par un pointeur". Je dois dire que je n'avais pas de bonne réponse technique à la question, alors j'ai fait une supposition.

J'ai dit que le temps d'accès sera probablement le même que celui d'une variable/identifiant normale, qui est un pointeur vers l'adresse mémoire où la valeur est stockée, tout comme un pointeur. En d'autres termes, en termes de vitesse, ils ont tous deux les mêmes performances, et les pointeurs ne sont différents que parce que nous pouvons spécifier l'adresse mémoire vers laquelle nous voulons qu'ils pointent.

L'interviewer n'a pas semblé très convaincu/satisfait de ma réponse (bien qu'il n'ait rien dit, il a juste continué à demander autre chose), c'est pourquoi j'ai pensé venir demander aux OS si ma réponse était exacte, et si non pourquoi (d'un point de vue théorique et technique).

43voto

paulsm4 Points 39422

Lorsque vous accédez à une "variable", vous recherchez l'adresse, puis vous récupérez la valeur.

N'oubliez pas qu'un pointeur est une variable. Donc en fait, vous :

a) rechercher l'adresse (de la variable pointeur),

b) récupérer la valeur (l'adresse stockée à cette variable)

... et puis ...

c) récupérer la valeur à l'adresse indiquée.

Donc oui, l'accès par "pointeur" (plutôt que directement) implique (un peu) de travail supplémentaire et un temps (légèrement) plus long.

C'est exactement la même chose, qu'il s'agisse d'une variable pointeur (C ou C++) ou d'une variable référence (C++ uniquement).

Mais la différence est extrêmement faible.

35voto

LaC Points 7191

Une variable ne doit pas nécessairement vivre dans la mémoire principale. Selon les circonstances, le compilateur peut la stocker dans un registre pour tout ou partie de sa vie, et l'accès à un registre est beaucoup plus rapide que l'accès à la RAM.

28voto

dewtell Points 721

Ignorons l'optimisation pour un moment, et pensons simplement à ce que la machine abstraite doit faire pour référencer une variable locale par rapport à une variable via un pointeur (local). Si nous avons des variables locales déclarées comme :

int i;
int *p;

lorsque nous référençons la valeur de i, le code non optimisé doit aller chercher la valeur qui se trouve (disons) à 12 au-delà du pointeur de pile actuel et la charger dans un registre pour que nous puissions travailler avec elle. Alors que lorsque nous faisons référence à *p, le même code non optimisé doit aller chercher la valeur de p à 16 après le pointeur de pile actuel, la charger dans un registre, puis aller chercher la valeur vers laquelle pointe le registre et la charger dans un autre registre afin de pouvoir travailler avec elle comme avant. La première partie du travail est la même, mais l'accès au pointeur implique conceptuellement une étape supplémentaire qui doit être effectuée avant que nous puissions travailler avec la valeur.

C'était, je pense, le but de la question de l'interview - voir si vous compreniez la différence fondamentale entre les deux types d'accès. Vous pensiez que l'accès à une variable locale impliquait une sorte de recherche, ce qui est le cas, mais l'accès à un pointeur implique ce même type de recherche pour obtenir la valeur du pointeur avant de pouvoir commencer à chercher l'objet pointé. En termes simples, non optimisés, l'accès au pointeur va être plus lent à cause de cette étape supplémentaire.

Maintenant, avec l'optimisation, il peut arriver que les deux temps soient très proches ou identiques. Il est vrai que si un autre code récent a déjà utilisé la valeur de p pour référencer une autre valeur, vous pouvez déjà trouver p dans un registre, de sorte que la recherche de *p via p prend le même temps que la recherche de i via le pointeur de pile. De la même manière, si vous avez récemment utilisé la valeur de i, vous pouvez déjà trouver il dans un registre. Et si la même chose peut être vraie pour la valeur de *p, l'optimiseur ne peut réutiliser sa valeur du registre que s'il est sûr que p n'a pas changé entre-temps. Il n'a aucun problème à réutiliser la valeur de i. En bref, si l'accès aux deux valeurs peut prendre le même temps avec l'optimisation, l'accès à la variable locale ne sera presque jamais plus lent (sauf dans des cas vraiment pathologiques), et peut très bien être plus rapide. C'est donc la réponse correcte à la question de l'enquêteur.

En présence de hiérarchies de mémoire, la différence de temps peut devenir encore plus prononcée. Les variables locales seront situées à proximité les unes des autres sur la pile, ce qui signifie que vous avez de grandes chances de trouver l'adresse dont vous avez besoin déjà dans la mémoire principale et dans le cache la première fois que vous y accédez (à moins qu'il ne s'agisse de la toute première variable locale à laquelle vous accédez dans cette routine). Il n'y a pas une telle garantie avec l'adresse vers laquelle pointe le pointeur. À moins qu'elle n'ait été accédée récemment, vous devrez peut-être attendre un manque de cache, ou même un défaut de page, pour accéder à l'adresse pointée, ce qui pourrait la rendre plus lente de plusieurs ordres de grandeur par rapport à la variable locale. Non, cela n'arrivera pas tout le temps - mais c'est un facteur potentiel qui pourrait faire une différence dans certains cas, et c'est aussi un point qui pourrait être soulevé par un candidat en réponse à une telle question.

Maintenant, qu'en est-il de la question soulevée par d'autres commentateurs : quelle importance cela a-t-il ? Il est vrai que, pour un accès unique, la différence sera minuscule en termes absolus, comme un grain de sable. Mais vous mettez suffisamment de grains de sable ensemble et vous obtenez une plage. Et bien que (pour continuer la métaphore) si vous cherchez quelqu'un qui peut courir rapidement sur une route de plage, vous ne voulez pas quelqu'un qui sera obsédé par le fait de balayer chaque grain de sable de la route avant de pouvoir commencer à courir, vous voulez quelqu'un qui sera conscient quand il ou elle court inutilement dans des dunes jusqu'aux genoux. Les profileurs ne vous sauveront pas toujours - en termes métaphoriques, ils sont bien meilleurs pour reconnaître un seul gros rocher que vous devez contourner que pour remarquer une multitude de petits grains de sable qui vous embourbent. J'aimerais donc avoir dans mon équipe des personnes qui comprennent ces questions à un niveau fondamental, même si elles font rarement l'effort d'utiliser ces connaissances. N'arrêtez pas d'écrire du code clair dans la quête de la micro-optimisation, mais soyez conscient du genre de choses qui peuvent coûter de la performance, en particulier lors de la conception de vos structures de données, et sachez si vous obtenez une bonne valeur pour le prix que vous payez. C'est pourquoi je pense que c'était une question d'entretien raisonnable, pour explorer la compréhension du candidat sur ces questions.

23voto

Marcin Deptuła Points 6449

Ce que paulsm4 et LaC ont dit + un peu d'asm :

    int y = 0;
mov         dword ptr \[y\],0  
    y = x;
mov         eax,dword ptr \[x\]   ; Fetch x to register
mov         dword ptr \[y\],eax   ; Store it to y
    y = \*px;
mov         eax,dword ptr \[px\]  ; Fetch address of x 
mov         ecx,dword ptr \[eax\] ; Fetch x 
mov         dword ptr \[y\],ecx   ; Store it to y

Non pas que cela ait beaucoup d'importance d'un autre côté, mais c'est probablement plus difficile à optimiser (par exemple, vous ne pouvez pas garder la valeur dans le registre du processeur, car le pointeur pointe simplement vers un endroit de la mémoire). Donc le code optimisé pour y = x ; pourrait ressembler à ceci :

mov dword ptr [y], ebx - si nous supposons que la var locale x a été stockée dans ebx

3voto

Adrian Regan Points 1574

Je pense que l'interviewer attendait que vous mentionniez le mot enregistrez . Ainsi, si vous déclarez une variable en tant que variable de registre, le compilateur fera tout son possible pour s'assurer qu'elle est stockée dans un registre du CPU.

Un peu de discussion sur l'accès au bus et la négociation pour d'autres types de variables et de pointeurs aurait aidé à cadrer le tout.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X