12
votes

Vecteur :: opérateur [] au-dessus de l'opérateur

Apparemment, après avoir profilé mon code (calcul scientifique) C ++, 25% (!) du temps est dépensé avec des appels vers vecteur :: opérateur [] . Vrai, mon code dépense tout son temps de lecture et d'écriture dans vecteur s (et quelques vecteur s), mais je voudrais Toujours savoir s'il y a censé être censé être une dépendance significative de opérateur [] par rapport aux tableaux de style C?

(J'ai vu une autre question connexe à ce sujet, mais concernant [] vs at () - mais apparemment même [] est trop lent pour moi ?!)

merci, Antony

(EDIT: Juste pour info: Utilisation de g ++ -o3 Version 4.5.2 sur Ubuntu)


5 commentaires

J'espère que vous vous êtes assuré de transformer des optimisations sur :) serait bon de connaître Compiler & OS aussi.


Je doute sincèrement que cela soit lié à tout surhead d'appeler [] , c'est probablement purement sur le nombre numéro de fois que vous accédez aux vecteurs. Peut-on chercher à régler votre algorithme pour réduire le nombre de fois que vous devez accéder?


Quelle est votre mise en œuvre stl? Et que profilez-vous? Typiquement opérateur [] n'aura pratiquement pas de surcharge perceptible sur l'inscription à une matrice C-TRAY dans une version optimisée. L'acte de mesure pourrait inhiber les optimisations (en fonction de la manière dont cela est fait) ou que vous pouvez utiliser une STL avec la vérification des erreurs agressives (Microsoft dans certaines configurations / versions par exemple).


Quelle est la taille des vecteurs? Accès séquentiel ou aléatoire?


Vecteurs relativement petits (quelques milliers d'éléments au plus, souvent seulement à quelques douzaines), avec une grande partie de l'accès aléatoire.


5 Réponses :


12
votes

Dans un compilateur moderne, en mode de libération, avec optimisations activées, il y a NO Overhead à l'aide de Opérateur [] par rapport aux pointeurs bruts: l'appel est complètement inlincé et résout à juste un accès de pointeur.

Je suppose que vous êtes en quelque sorte la copie de la valeur de retour dans l'affectation et que ce provoque la réelle fois de 25% consacrée à l'instruction. [non pertinent Pour float et int ]

ou le reste de votre code est tout simplement flamboyant rapide.


2 commentaires

Cela suppose bien sûr que vous n'utilisez pas une implémentation de la bibliothèque standard ésotérique avec des contrôles d'exécution ajoutés.


Je pense que l'OP a mentionné quelque chose sur float et int , doute que la copie pourrait être le problème ici (?)



1
votes

Un accès de matrice pure est une lecture de mémoire (presque) directe, alors que l'opérateur [] est une méthode de membre de vecteur <>.

Si elle est correctement inline, elle devrait être la même, sinon, la surcharge est très significative pour le travail intensif de calcul.


0 commentaires

9
votes

Oui, il y aura des frais généraux comme typiquement un code> code> contiendra un pointeur sur un tableau alloué de manière dynamique où comme une matrice est juste "là". Cela signifie qu'il y aura généralement une déréférence supplémentaire de mémoire dans vecteur :: opérateur [] code> sur [] code> sur un tableau. (Notez que si vous avez un pointeur em> à un tableau, cela n'est généralement pas meilleur qu'un vecteur code> code>.)

Si vous effectuez plusieurs accès via le même Vecteur code> ou pointeur dans la même section du code sans que le vecteur ait peut-être besoin de réaffecter, alors le coût de cette désarférence supplémentaire peut être partagé sur les multiples accès et pourrait bien être négligeable. P>

.globl _Z5test1i
        .type   _Z5test1i, @function
_Z5test1i:
        movq    vf(%rip), %rax
        movss   (%rax,%rdi,4), %xmm0
        ret
        .size   _Z5test1i, .-_Z5test1i

.globl _Z5test2i
        .type   _Z5test2i, @function
_Z5test2i:
        movss   af(,%rdi,4), %xmm0
        ret
        .size   _Z5test2i, .-_Z5test2i

.globl _Z5test3i
        .type   _Z5test3i, @function
_Z5test3i:
        movq    pf(%rip), %rax
        movss   (%rax,%rdi,4), %xmm0
        ret
        .size   _Z5test3i, .-_Z5test3i


0 commentaires

8
votes

En général, il ne devrait y avoir aucune différence signé. Les différences peuvent se produire dans la pratique, cependant, pour diverses raisons, en fonction de la Le compilateur optimise un peu de code spécifique. Un important possible différence: vous profilez, ce qui signifie que vous exécutez code instrumenté. Je ne sais pas quel profileur vous utilisez, mais c'est fréquent pour le compilateur pour éteindre l'inlinage pour diverses raisons lorsque Instruire pour profilage. Êtes-vous sûr que ce n'est pas le cas ici, et que cela entraîne artificiellement l'indexation de comparaître prendre un plus grand nombre de temps que s'il a été inliné.


2 commentaires

+1 pour la note sur le code instrumenté. Pour le code rapide, la seule option viable est un profileur statistique passif (E.G. OPROFILE). Toute forme d'instrumentation rendra la mesure inutile.


J'ai compilé avec option -pg et utilisé gprof. C'est peut-être la question en effet.



7
votes

std :: vecteur :: opérateur [] doit être assez efficace, mais le compilateur doit être paranoïaque et pour chaque appel apporté à une fonction, il doit supposer que le vecteur aurait pu être déplacé ailleurs en mémoire.

par exemple dans ce code xxx

si le code de foo n'est pas connu à l'avance, le compilateur est obligé de recharger L'adresse du vecteur commence à chaque fois car le vecteur aurait pu être réaffecté comme une conséquence du code intérieur foo () .

Si vous savez que le vecteur ne va pas être déplacé en mémoire ou réaffectée, vous pouvez extraire cette opération de recherche avec quelque chose comme xxx

avec cette approche une opération de recherche de mémoire peut être enregistrée ( vptr est susceptible de se retrouver dans un registre de la boucle entière).

Un autre motif d'inefficacité peut être en cache. Pour voir si c'est le problème, une astuce facile est de simplement sur-allouer vos vecteurs par un nombre inégal d'éléments.

La raison est qu'en raison de la fonctionnement de la mise en cache si vous avez de nombreux vecteurs avec E. 4096 Éléments Toutes les mêmes bits à faible ordre de l'adresse de l'adresse et vous risquez de perdre beaucoup de vitesse en raison des invalidations de ligne de cache. Par exemple, cette boucle sur mon PC xxx

s'exécute dans environ 8,1 secondes si n == 8191 et en 3,2 secondes si n == 10000 . Notez que la boucle interne est toujours de 0 à 999, indépendamment de la valeur de n ; Ce qui est différent n'est que l'adresse de la mémoire.

Selon le processeur / architecture, j'ai observé même 10x ralentissements en raison de la corbeille de cache.


0 commentaires