Le code suivant effectue certaines copies d'un tableau de zéros interprétés comme floats à un autre et imprime la synchronisation de cette opération. Comme je l'ai vu de nombreux cas où les boucles de non-Op sont simplement optimisées par les compilateurs, y compris GCC, j'attendais que, à un moment donné de changer mon programme de tableaux de copie, il arrête de faire la copie.
#include <iostream> #include <cstring> #include <sys/time.h> static inline long double currentTime() { timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return ts.tv_sec+(long double)(ts.tv_nsec)*1e-9; } int main() { size_t W=20000,H=10000; float* data1=new float[W*H]; float* data2=new float[W*H]; memset(data1,0,W*H*sizeof(float)); memset(data2,0,W*H*sizeof(float)); long double time1=currentTime(); for(int q=0;q<16;++q) // take more time for(int k=0;k<W*H;++k) data2[k]=data1[k]; long double time2=currentTime(); std::cout << (time2-time1)*1e+3 << " ms\n"; delete[] data1; delete[] data2; }
4 Réponses :
Pourquoi vous attendez-vous au compilateur d'optimiser cela? Il est généralement très difficile de prouver que les écrivies d'adresses de mémoire arbitraires sont un "no-op". Dans votre cas, il serait possible, mais il faudrait que le compilateur trace les adresses de mémoire en tas par Après tout, vous dites au compilateur explicitement em> que vous souhaitez allouer la mémoire et à y écrire. Comment le compositeur pauvre est-il de savoir que vous avez menté à cela? P>
En particulier, le problème est que la mémoire du tas pourrait être aliasée à beaucoup d'autres choses. Il se trouve être privé à votre processus, mais comme je l'ai dit ci-dessus, prouvant qu'il s'agit de beaucoup de travail pour le compilateur, contrairement à la mémoire de la fonction locale. P> nouveau code> (qui est à nouveau dur em> puisque ces adresses sont générées au moment de l'exécution) et il n'y a vraiment aucune incitation à ce que ce soit. P>
Il n'y a plus de référence à cette mémoire, c'est comment.
Je m'attends à ce que le compilateur vérifie les dépendances de données et ne dis que "je ne sais pas" sur volatile code> mémoire. Ainsi, il semble assez naturel d'attendre une optimisation assez agressive.
@Violetgirafe le prouver. Astuce: C'est vraiment, vraiment difficile.
Dans ce cas, c'est vraiment, vraiment simple.
@MarkusMayR volatile code> signifie également que tout écrit à celui-ci doit être dans l'ordre prescrit et compter. Cela peut être un pointeur à une région MMIO.
@Konradrudolph Quoi qu'il en soit, vos préoccupations concernant le nouveau ne semblent pas être (entièrement) valables. J'ai mis à jour la question en supprimant les deux Memset code> et
nouveaux code> opérateurs.
@Ruslan je ne peux pas croire que cela fonctionne et ne se sépare pas. Le tableau est de Waaay à gros pour être géré sur la pile.
@freakish Oui, ulimit code> est le traitement
@Ruslan ah, bien sûr. Retour au sujet: essayez-vous de résoudre un problème réel? Sinon, ne gardez pas à l'esprit que probablement personne ne sera probablement en mesure de répondre à cette question. Sauf pour les personnes qui ont écrit le compilateur. Peut-être que vous devriez leur demander directement?
@freakish, j'essaie simplement de comprendre la logique à propos de savoir quand s'attendre à ce que certaines optimisations ruinent la référence et quand on s'attend à ce que cela fonctionne. Depuis la réponse de Manlio, il semble que Clang optimise cette optimisation.
@Ruslan Oui, c'est pourquoi j'ai dit que vous devriez demander aux personnes qui ont créé G ++ ( La liste de diffusion ). Ce n'est pas un problème général. Le problème est spécifique à g ++. De plus, je pense que vous devriez supposer le moins possible sur les optimisations. Off-sujets: Clang est un très bon compilateur, vous devriez l'essayer.
@freakish peut-être bon, mais pas assez bon pour pouvoir remplacer GCC partout.
@Ruslan je ne suis pas convaincu qu'Invira ma réponse. Cela montre simplement que même sur la pile i> Le compilateur n'opère tout simplement pas aussi loin que vous le pensez.
@KonRADRUDOLPH Bien sûr, je n'ai pas dit que cela doit optimiser. Je me demandais si cela ne devait pas.
@Karolyhorvath Non, c'est tout simplement faux, à moins que le compilateur traite à la fois opérateur Nouveau code> et
MEMSET code> comme intrinsique (qu'il est autorisé à faire, mais cela ne signifie souvent pas).
Eh bien, tout ce qu'il faut savoir, c'est que la mémoire n'est pas aliasée. Je m'attendrais à ce que beaucoup du compilateur (à la fois pour le nouveau et le memset). Après cela, c'est une déduction simple.
"Après tout, vous dites au compilateur explicitement que vous voulez allouer de la mémoire et écrivez-y. Comment le pauvre compilateur est-il de savoir que vous allez mentir?" - Tout comme vous explicitement i> Dites au compilateur d'allouer une variable locale et d'écrire à elle. et puis jamais l'utiliser à nouveau. Est-ce un mensonge?
Le seul moyen de savoir que le compilateur pouvait savoir qu'il s'agit d'un non-op est s'il savait que memset code> fait. Pour que cela se produise, la fonction doit être définie dans un en-tête (et elle n'est généralement pas), soit elle doit être traitée comme une intrinsèque spéciale par le compilateur. Mais sauf ces astuces, le compilateur vient d'appeler une fonction inconnue que pourrait em> avoir des effets secondaires et faire des choses différentes pour chacun des deux appels. P>
Je dirais que la question ne concerne pas principalement le memset code>, mais sur la data2 [k] = data1 [k] code> boucle (car
data2 code> n'est plus accessible après cela).
Commentaire Memset code> Les appels ne changent pas de comportement (bien que 6950 ms modifient à 5500 ms pour une raison quelconque).
Le même argument détient pour opérateur nouveau code>. Et il n'y a pas vraiment de motivation pour faire une allocation de mémoire un compilateur intrinsèque afin que le compilateur ne connaisse probablement pas toutes les subtilités de la procédure d'allocation de mémoire du système d'exploitation.
@KonRADRUDOLPH: En fait, au-delà de l'utilisation intrinsique, les compilateurs trichent. Par exemple dans LLVM, MALLOC code> et
gratuit code> (et les noms mutionnés de
Nouveau code> et
Suppr code>) sont connus de la Optimiseur de sorte que si elle se rend compte que la mémoire est inutilisée (après optimisation par exemple), elle optimise également l'allocation / la répartition. Ceci enfreint tout ce qui est en violation de tout instrument qui dépendrait des effets secondaires (et avec
ld_library_preload code> ils ne peuvent pas savoir à ce sujet), mais c'est une stratégie utilisée.
@Matthieuum. Bien sûr qu'ils font. Je voulais dire que vous ne pouvez pas attendre i> tricher. Je suis légèrement surpris que LLVM se déroule dans MALLOC code> et
gratuit code> mais d'accord.
@KonRADRUDOLPH: Je suis également surpris aussi, je ne sais pas s'il existe un libellé spécifique dans la norme pour permettre leur élision (comme pour les constructeurs de copies), je n'ai certainement jamais rien vu mentionné. Je sais aussi que LLVM pourrait modifier également une allocation de tas à une étalonnage.
Quoi qu'il en soit, ce n'est pas impossible (Clang ++ version 3.3):
... const size_t W=20000; const size_t H=10000; float data1[W*H]; float data2[W*H]; ...
Le code dans cette question a changé un peu, invalidez les réponses correctes. Cette réponse s'applique à la 5ème version: comme le code tente actuellement de lire la mémoire ininitialisée, un optimiseur peut raisonnablement supposer que des choses inattendues se produisent. p>
De nombreuses étapes d'optimisation ont un modèle similaire: il existe un modèle d'instructions correspondant à l'état actuel de la compilation. Si le motif correspond à un moment donné, le motif correspondant est (paramétriquement) remplacé par une version plus efficace. Un exemple très simple d'un tel motif est la définition d'une variable qui n'est pas utilisée ultérieurement; Le remplacement dans ce cas est simplement une suppression. P>
Ces motifs sont conçus pour le code correct. Sur un code incorrect, les modèles peuvent simplement manquer de correspondre ou peuvent correspondre à des voies entièrement inattendues. Le premier cas ne conduit à aucune optimisation, le second cas peut entraîner des résultats totalement imprévisibles (certainement si le code modifié si optimisé) p>
Ce n'est pas de notre point de vue, mais le compilateur ne connaît pas et ne peut pas savoir que le contenu du morceau de mémoire à partir de données1 et de données2 est déjà égal.
Je ne peux pas donner une réponse définitive, mais a) toute optimisation n'est pas obligatoire. b) Vous avez deux
Nouveau code> mais aucun
Supprimer code>.
@deviantfan, je comptais sur la mémoire libérée à la sortie, bien qu'il ne semble pas être garanti par la norme.
Même si des systèmes d'exploitation majeurs peuvent faire cela, il est tout simplement faux d'omettre les supports. Et pour tout ce que je sais, cela pourrait même résoudre votre problème d'optimisation ("pourrait", pas "volonté").
@deviantfan j'ai vérifié, ça ne le fait pas.
@ PAN-: il sait cependant que le résultat de la calcul de données2 n'est pas utilisé. Je suis d'accord avec Op, ce type de code est généralement optimisé dans ma pratique et je dois prendre des mesures particulières pour m'assurer que cela ne se produit pas lors de la rédaction de ces points de repère.
Je suis curieux de le savoir. Voulez-vous donner une meilleure chance de combattre si elles ne sont pas dynamiques? Odd, d'accord, mais sérieusement, vraiment curieux (je penserais que cela ne fait aucune différence, mais la peine de découvrir:
statique code>).
VLAS? Ce n'est même pas portable C ++ .. et conservez le code d'origine (aussi?) C'est déroutant si la question change à la volée.
Si vous déclarez w, h comme
consexpr code> g ++ optimise votre code. Ceci, cependant, n'est pas une réponse à votre question.
Pourquoi continuez-vous à changer l'allocation de la matrice? Votre version actuelle n'est même pas la zéro des tableaux; Ce n'est pas une surprise que le compilateur n'oblise pas la boucle loin.
@ user2357112 Il s'agissait des tentatives de simplification du code maximum