9
votes

sur le coût de la fonction virtuelle

Si j'appelle une fonction virtuelle 1000 fois dans une boucle, vais-je souffrir de la surcharge de recherche à la fourche 1000 fois ou une seule fois?


1 commentaires

Je serais plus préoccupé par le cache Miss et la cible de saut mal prédite que le temps nécessaire pour chercher en soi. Et ces problèmes dépendent beaucoup des schémas précis des accès antérieurs. La réponse standard sur la comparaison de la micro-performance était "Mesure" et c'est maintenant "mesurer et faire attention à ce que vous mesurez est vraiment caractéristique du déploiement". Vous aurez également une dépendance sur le modèle précis du processeur, ce que les autres notes sont en cours d'exécution, des effets SMT, ...


7 Réponses :


3
votes

Si le compilateur peut déduire que l'objet sur lequel vous appelez la fonction virtuelle ne change pas, alors, en théorie , il devrait être capable de hisser la recherche de la boucle .

Si votre compilateur particulier est effectivement quelque chose que vous ne pouvez savoir que en examinant le code de montage qu'il produit.


3 commentaires

... ou en profilant. Écrivez un code qui doit faire 1000 recherches et comparer.


Mais que compareriez-vous-tu contre? Vous ne pouvez pas le comparer à une fonction non virtuelle car cela appelle une adresse absolue par opposition à une adresse indirecte. Vous ne pouvez pas non plus la comparer contre le code qui appelle 1000 objets différents, car a) vous devez obtenir les adresses de ces objets de quelque part, ce qui prend du temps supplémentaire, et b) appeler 1000 objets différents est beaucoup moins caché, donc nous s'attendre à ce qu'il soit plus lent de toute façon.


Comparé contre une volatie foo *. Notez que vous devez d'abord vérifier 1000 appels non virtuels pour voir la quantité de surcharge que vous engagez en rechargeant ce sur chaque appel. Ensuite, comparez les appels virtuels volatile et non volatile de FOO * SOO * SOIX pour voir la quantité supplémentaire surhead la recherche à la fourchette.



8
votes

Le compilateur peut être capable d'optimiser - par exemple, ce qui suit est (au moins conceptuellement) optimisé optimisé: xxx

Cependant, d'autres cas sont plus difficiles: < Pré> xxx

La même optimisation conceptuelle est applicable, mais beaucoup plus difficile pour le compilateur à voir.

Bottom Line - Si vous vous souciez vraiment de cela, compilez votre code avec toutes les optimisations activées. et examinez la sortie d'assembleur du compilateur.


0 commentaires

6
votes

Le compilateur Visual C ++ (au moins par VS 2008) ne met pas en cache des recherches à table. Encore plus intéressant, il n'écrit pas les appels directs vers des méthodes virtuelles où le type statique de l'objet est scellé . Cependant, la surcharge réelle de la recherche de distribution virtuelle est presque toujours négligeable. L'endroit où vous voyez parfois un coup est dans le fait que les appels virtuels en C ++ ne peuvent pas être remplacés par des appels directs comme ils peuvent dans une machine virtuelle gérée. Cela signifie également aucune inlinage pour les appels virtuels.

Le seul moyen VRAI d'établir l'impact de votre application utilise un profileur.

En ce qui concerne les spécificités de votre question initiale: Si la méthode virtuelle que vous appelez est assez triviale que l'envoi virtuel elle-même entraîne un impact de performance mesurable, cette méthode est suffisamment faible que la tablette restera dans le cache du processeur tout au long de la boucle. Même si les instructions de montage pour tirer le pointeur de la fonction de la machine à livrer sont exécutées 1000 fois, l'impact de la performance sera beaucoup moins que (1000 * temps pour charger une table à fourche à partir de la mémoire système) . .


1 commentaires

Merci beaucoup pour votre réponse et vos commentaires. Je vérifierai que sur GCC quand je reçois un peu de temps.



0
votes

Je dirais que cela dépend de votre compilateur ainsi que sur le look de la boucle. Optimiser les compilateurs peut faire beaucoup pour vous et si l'appel VF est prévisible, le compilateur peut vous aider. Peut-être que vous pouvez trouver quelque chose sur les optimisations de votre compilateur dans votre documentation de compilateur.


1 commentaires

Je sais que ça "pourrait", je ne le fais pas si ça "le fait" m'aider.



1
votes

Je pense que le problème n'est pas une recherche à la fourchette car c'est une opération très rapide, notamment dans une boucle où vous avez toutes les valeurs requises sur le cache (si la boucle n'est pas trop complexe, mais si elle est complexe, la fonction virtuelle n'aurait pas d'impact sur la performance. beaucoup). Le problème est le fait que le compilateur ne peut pas aligner cette fonction dans la compilation.

Ceci est particulièrement un problème lorsque la fonction virtuelle est très petite (par exemple, ne renvoyant qu'une seule valeur). L'impact de performance relatif dans ce cas peut être énorme car vous avez besoin d'un appel de la fonction pour simplement retourner une valeur. Si cette fonction peut être inlinée, cela améliorerait beaucoup les performances.

Si la fonction virtuelle est la performance qui prend des performances, je ne me soucierais pas vraiment de circonstances.


0 commentaires

1
votes

Pour une étude sur la surcharge des appels de fonction virtuelle, je recommande le papier "Le coût direct des appels de fonction virtuelle en C ++"


0 commentaires

1
votes

Essayons d'essayer avec g ++ ciblage x86:

$ cat y.cpp
struct A
  {
    virtual void not_used(int);
    virtual void f(int);
  };

void foo(A &a)
  {
    for (unsigned i = 0; i < 1000; ++i)
      a.f(13);
  }
$ 
$ gcc -S -O3  y.cpp  # assembler output, max optimization
$ 
$ cat y.s
    .file   "y.cpp"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB0:
    .text
.LHOTB0:
    .p2align 4,,15
    .globl  _Z3fooR1A
    .type   _Z3fooR1A, @function
_Z3fooR1A:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    pushq   %rbx
    .cfi_def_cfa_offset 24
    .cfi_offset 3, -24
    movq    %rdi, %rbp
    movl    $1000, %ebx
    subq    $8, %rsp
    .cfi_def_cfa_offset 32
    .p2align 4,,10
    .p2align 3
.L2:
    movq    0(%rbp), %rax
    movl    $13, %esi
    movq    %rbp, %rdi
    call    *8(%rax)
    subl    $1, %ebx
    jne .L2
    addq    $8, %rsp
    .cfi_def_cfa_offset 24
    popq    %rbx
    .cfi_def_cfa_offset 16
    popq    %rbp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE0:
    .size   _Z3fooR1A, .-_Z3fooR1A
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .ident  "GCC: (GNU) 5.3.1 20160406 (Red Hat 5.3.1-6)"
    .section    .note.GNU-stack,"",@progbits
$


0 commentaires