J'ai lu que je devrais éviter l'opérateur d'incrémentation postfix pour des raisons de performances (dans certains cas).
Mais cela n'affecte-t-il pas la lisibilité du code? À mon avis:
for(int i = 0; i < 42; i++);
/* i will never equal 42! */
Ressemble mieux que:
for(int i = 0; i < 42; ++i);
/* i will never equal 42! */
Mais c'est probablement juste par habitude. Certes, je n'ai pas vu beaucoup utiliser ++i
.
Les performances sont-elles mauvaises pour sacrifier la lisibilité, dans ce cas? Ou suis-je simplement aveugle et ++i
est plus lisible que i++
?
Les faits:
i ++ et ++ i sont également faciles à lire. Vous ne l'aimez pas parce que vous n'y êtes pas habitué, mais il n'y a pratiquement rien de mal à l'interpréter, donc ce n'est plus un travail à lire ou à écrire.
Dans au moins certains cas, l'opérateur postfix sera moins efficace.
Cependant, dans 99,99% des cas, cela n'aura pas d'importance car (a) il agira de toute façon sur un type simple ou primitif et ce n'est un problème que s'il copie un gros objet (b) il ne sera pas dans une performance partie critique du code (c) vous ne savez pas si le compilateur l'optimisera ou non, il peut le faire.
Ainsi, je suggère d'utiliser le préfixe à moins que vous n'ayez spécifiquement besoin que postfix soit une bonne habitude, juste parce que (a) c'est une bonne habitude d'être précis avec d'autres choses et (b) une fois dans une lune bleue, vous aurez l'intention d'utiliser postfix et faites-le dans le mauvais sens: si vous écrivez toujours ce que vous voulez dire, c'est moins probable. Il y a toujours un compromis entre performances et optimisation.
Vous devez faire preuve de bon sens et ne pas faire de micro-optimisation jusqu'à ce que vous en ayez besoin, mais ne soyez pas manifestement inefficace pour le plaisir. Cela signifie généralement: premièrement, exclure toute construction de code qui est inefficace de manière inacceptable, même dans le code non critique en temps (normalement quelque chose représentant une erreur conceptuelle fondamentale, comme passer des objets de 500 Mo par valeur sans raison); et deuxièmement, de toutes les autres façons d'écrire le code, choisissez la plus claire.
Cependant, ici, je pense que la réponse est simple: je pense que l'écriture d'un préfixe, sauf si vous avez spécifiquement besoin de postfix, est (a) très légèrement plus claire et (b) très légèrement plus susceptible d'être plus efficace, vous devriez donc toujours l'écrire par défaut, mais ne vous en faites pas si vous oubliez.
Il y a six mois, je pensais comme vous, qu'i ++ était plus naturel, mais c'est purement ce à quoi vous êtes habitué.
EDIT 1: Scott Meyers, dans "C++ plus efficace" auquel je fais généralement confiance, dit que vous devriez en général éviter d'utiliser l'opérateur postfix sur les types définis par l'utilisateur (car la seule implémentation sensée de la fonction d'incrémentation postfix est de faire un copie de l'objet, appelez la fonction d'incrémentation du préfixe pour effectuer l'incrémentation et renvoyez la copie, mais les opérations de copie peuvent être coûteuses).
Donc, nous ne savons pas s'il existe des règles générales concernant (a) si cela est vrai aujourd'hui, (b) si cela s'applique également (moins) aux types intrinsèques (c) si vous devez utiliser "++" sur rien de plus qu'une classe d'itérateurs légers. Mais pour toutes les raisons que j'ai décrites ci-dessus, peu importe, faites ce que j'ai dit auparavant.
EDIT 2: Cela fait référence à la pratique générale. Si vous pensez que cela importe dans certains cas spécifiques, vous devez le profiler et le voir. Le profilage est facile et bon marché et fonctionne. Déduire des premiers principes ce qui doit être optimisé est difficile et coûteux et ne fonctionne pas.
Toujours coder le programmeur en premier et l'ordinateur en second.
S'il y a une différence de performances, une fois que le compilateur a jeté son œil expert sur votre code, ET vous pouvez le mesurer ET c'est important - alors vous pouvez le changer.
GCC produit le même code machine pour les deux boucles.
Code C
int main(int argc, char** argv)
{
for (int i = 0; i < 42; i++)
printf("i = %d\n",i);
for (int i = 0; i < 42; ++i)
printf("i = %d\n",i);
return 0;
}
Code d'assemblage (avec mes commentaires)
cstring
LC0:
.ascii "i = %d\12\0"
.text
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $36, %esp
call L9
"L00000000001$pb":
L9:
popl %ebx
movl $0, -16(%ebp) // -16(%ebp) is "i" for the first loop
jmp L2
L3:
movl -16(%ebp), %eax // move i for the first loop to the eax register
movl %eax, 4(%esp) // Push i onto the stack
leal LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
movl %eax, (%esp) // Push the address of the format string onto the stack
call L_printf$stub // call printf
leal -16(%ebp), %eax // make the eax register point to i
incl (%eax) // increment i
L2:
cmpl $41, -16(%ebp) // compare i to the number 41
jle L3 // jump to L3 if less than or equal to 41
movl $0, -12(%ebp) // -12(%ebp) is "i" for the second loop
jmp L5
L6:
movl -12(%ebp), %eax // move i for the second loop to the eax register
movl %eax, 4(%esp) // Push i onto the stack
leal LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
movl %eax, (%esp) // Push the address of the format string onto the stack
call L_printf$stub // call printf
leal -12(%ebp), %eax // make eax point to i
incl (%eax) // increment i
L5:
cmpl $41, -12(%ebp) // compare i to 41
jle L6 // jump to L6 if less than or equal to 41
movl $0, %eax
addl $36, %esp
popl %ebx
leave
ret
.section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
.indirect_symbol _printf
hlt ; hlt ; hlt ; hlt ; hlt
.subsections_via_symbols
Ne vous inquiétez pas des performances, disons 97% du temps. L'optimisation prématurée est la racine de tout Mal.
- Donald Knuth
Maintenant que cela est hors de notre chemin, faisons notre choix sainement :
++i
: incrément de préfixe , incrémente la valeur actuelle et donne le résultati++
: incrément du suffixe , copiez la valeur, incrémentez la valeur actuelle, donne la copieÀ moins qu'une copie de l'ancienne valeur ne soit requise, utiliser l'incrément de suffixe est un moyen détourné de faire avancer les choses.
Imprécision vient de la paresse, utilisez toujours la construction qui exprime votre intention de la manière la plus directe, il y a moins de chance que le futur mainteneur ne puisse mal comprendre votre intention initiale.
Même si c'est (vraiment) mineur ici, il y a des moments où j'ai été vraiment perplexe en lisant le code: je me demandais vraiment si l'intention et l'express réel coïncidaient, et bien sûr, après quelques mois, ils (ou moi) ne se souvenait pas non plus ...
Donc, peu importe si cela vous convient ou non. Embrasser BAISER . Dans quelques mois, vous aurez évité vos anciennes pratiques.
En C++, vous pourriez faire une différence de performances substantielle s'il y a des surcharges d'opérateurs impliquées, en particulier si vous écrivez du code basé sur des modèles et ne savez pas quels itérateurs peuvent être transmis. La logique derrière tout itérateur X peut être à la fois substantiel et significatif, c'est-à-dire lent et impossible à optimiser par le compilateur.
Mais ce n'est pas le cas en C, où vous savez que ce ne sera qu'un type trivial, et la différence de performances est triviale et le compilateur peut facilement s'optimiser.
Donc, un conseil: vous programmez en C ou en C++, et les questions concernent l'un ou l'autre, pas les deux.
Les performances de l'une ou l'autre opération dépendent fortement de l'architecture sous-jacente. Il faut incrémenter une valeur qui est stockée en mémoire, ce qui signifie que le goulot d'étranglement de von Neumann est le facteur limitant dans les deux cas.
Dans le cas de ++ i, nous devons
Fetch i from memory
Increment i
Store i back to memory
Use i
Dans le cas d'i ++, nous devons
Fetch i from memory
Use i
Increment i
Store i back to memory
Les opérateurs ++ et - tracent leur origine au jeu d'instructions PDP-11. Le PDP-11 pourrait effectuer un post-incrémentation automatique sur un registre. Il pourrait également effectuer une pré-décrémentation automatique sur une adresse effective contenue dans un registre. Dans les deux cas, le compilateur ne pouvait tirer parti de ces opérations au niveau de la machine que si la variable en question était une variable "registre".
Si vous voulez savoir si quelque chose est lent, testez-le. Prenez un BigInteger ou équivalent, collez-le dans une boucle similaire pour les deux idiomes, assurez-vous que l'intérieur de la boucle n'est pas optimisé et chronométrez les deux.
Après avoir lu l'article, je ne le trouve pas très convaincant, pour trois raisons. Premièrement, le compilateur doit être en mesure d'optimiser la création d'un objet qui n'est jamais utilisé. Deux, le i++
le concept est idiomatique pour numérique pour les boucles, donc les cas que je peux voir réellement affectés sont limités à. Troisièmement, ils fournissent un argument purement théorique, sans aucun chiffre à l'appui.
Sur la base de la raison n ° 1 en particulier, je suppose que lorsque vous effectuez réellement le timing, ils seront juste à côté les uns des autres.