Je vérifie la différence entre deux implémentations de descente de gradient, ma supposition était qu'avec après l'optimisation du compilateur, les deux versions de l'algorithme seraient équivalentes.
Pour ma surprise, la version récursive était nettement plus rapide. Je n'ai pas rejeté un défaut réel sur aucune des versions ni même dans la façon dont je mesure le temps. Pouvez-vous me donner des idées s'il vous plaît? P>
Ceci est mon code: P>
Minimum : 0.000487 Minimum : 0.000487 time_iter:127 ms, time_rec:19 ms, ratio (dif1/dif2) :6.68421 End.
7 Réponses :
Pour une chose, votre étape récursive manque un code> retour code>: devrait être: p> Cette supervision entraîne la valeur de retour du Descgrad code> indéfini, de sorte que le compilateur doit à peine générer du code pour celui-ci;) p> p>
Je viens de tester cette théorie et cela ne change pas l'observation de l'OP.
@ user112358132134: assez juste. Après avoir testé localement, je peux reproduire vos conclusions.
Je ne pense pas que ce soit nécessaire car la condition d'arrêt est "retour xnew" et les deux fonctions ont renvoyé la bonne valeur. De plus, j'ai à nouveau essayé avec le code que vous avez proposé et il n'y avait pas de changement sur la performance.
@Pedrom: Il est absolument essentiel d'avoir le code> code> là pour obtenir un programme correct. Cependant, il ne semble pas affecter le temps d'exécution, comme je l'ai déjà dit. S'il produit la sortie correcte, ce n'est que par hasard.
@Magnushoff Désolé pour le désaccord Magnus, mais le compilateur le résout pour moi ou que ce n'est pas nécessaire. Ce que je m'attend à ce que l'environnement de pile soit réutilisé sur chaque appel puis de retourner la valeur pour arrêter la récursivité puisque je ne pars pas d'opérations en attente. Cela pourrait être faux mais s'il vous plaît si ce n'est pas le cas pourquoi il fonctionne?
@Pedrom: Cela fonctionne probablement exactement comme si vous l'envisagez. Cependant, cela n'est pas mandaté par la norme linguistique. Selon la norme, le rendement manquant provoque "comportement indéfini": la norme ne se soucie pas de ce que le compilateur fait dans ce cas. Donc, il pourrait compiler i> pour rendre le programme que vous avez destiné (qu'il semble faire), ou il pourrait ne pas i>. Si vous souhaitez vous garantir que le programme fonctionne correctement à l'avenir, sur différents ordinateurs, sur différents compilateurs, mettez le retour de là. En tout cas, il ne devrait pas ralentir les choses;)
@Magnushoff merci pour la perspicacité :) et je pense que je pense que c'est une violation de la norme, c'était juste un code éloigné que je faisais et j'ai eu cette question au milieu ... aussi merci pour la réponse. :)
Dans de nombreux cas, sur les misses de cache de matériel moderne sont le facteur limitant des performances des constructions de petite boucle. Une implémentation récursive est moins susceptible de créer des manque de cache sur le chemin d'instructions. P>
Alors qu'une boucle serrée ne sera pas? Je n'ai pas acheté ça.
est ma façon de mesurer le temps faux? P> blockQuote>
oui. Dans les épisodes courtes, vous mesurez, le planificateur peut avoir un impact massif sur votre programme. Vous devez soit rendre votre test beaucoup plus longtemps à la moyenne de telles différences, ou d'utiliser
clock_process_cuTime_id code> pour mesurer le temps de la CPU utilisé par votre processus. P>
Cela semble juste mais cela n'explique pas la différence significative entre eux? Les deux fois ne sont même pas proches
@Pedrom: Oui, mais un processus non invitionné n'obtienne aucun temps de processeur pendant 0,1 seconde est tout à fait possible. De plus, comme Totowtwo a noté, la déclaration printf code> introduit davantage de retard comme des tampons potentiellement non engagés, qui introduisent des ralentissements plus imprévisibles. Votre algorithme ne peut prendre que 10 ms pour les deux variantes et le reste est le bruit, il n'y a donc aucune signification dans la différence.
Yeap tu es ... Le printf faisait le bruit.
J'ai compilé et exécuté votre code localement. Déplacer le Une erreur centrale dans votre timing est que vous mesurez la bête complexe de mon printf code> à l'extérieur du bloc chronométré permet aux deux versions exécutées dans ~ 5ms à chaque fois.
printf code > Et son runtime narfs le code que vous essayez de mesurer. p>
Main () code> -function ressemble maintenant à ceci: p>
int main() {
struct timespec s1, e1, s2, e2;
double d = 0.0;
clock_gettime(CLOCK_MONOTONIC, &s1);
d = descgraditer(100,99,0.01,0.00001);
clock_gettime(CLOCK_MONOTONIC, &e1);
printf("Minimum : %f\n", d);
clock_gettime(CLOCK_MONOTONIC, &s2);
d = descgrad(100,99,0.01,0.00001);
clock_gettime(CLOCK_MONOTONIC, &e2);
printf("Minimum : %f\n",d);
uint64_t dif1 = timespecDiff(&e1,&s1) / 1000;
uint64_t dif2 = timespecDiff(&e2,&s2) / 1000;
printf("time_iter:%llu ms, time_rec:%llu ms, ratio (dif1/dif2) :%g\n", dif1,dif2, ((double) ((double)dif1/(double)dif2)));
printf("End. \n");
}
Merci .. cela fait de la tonne de sens!
Cette réponse est incorrecte. Je ne peux pas reproduire ses résultats, en particulier sur plusieurs itérations de fonction.
@ user112358132134 Qu'est-ce que vous ne pouviez pas reproduire exactement? Je pourrais en effet confirmer que ces imprimés ralentissaient l'exécution de la première fonction. Si vous commencez à la fois que le temps d'exécution de Printf doit être proche pour les deux fonctions.
J'ai posté une réponse. En bref, la différence d'exécution est le résultat de l'optimisation des appels queue de -O3.
Pour commencer, vous incluiez une Printf à l'époque que vous essayiez de mesurer. C'est toujours un non-nul géant, car il peut, et très probablement, suspendre votre processus en effectuant la sortie de la console. En fait, tout appel système peut complètement lancer des mesures de temps comme celles-ci.
Et deuxièmement, comme quelqu'un d'autre l'a mentionné, à ce court-circonscription, des interruptions de planificateur peuvent avoir un impact énorme. P>
Ceci est Pas parfait, mais essayez ceci pour votre principal et vous verrez qu'il y a vraiment très peu de différence. Lorsque vous augmentez le nombre de boucles, le rapport s'approche 1.0. P> } p> Deuxièmement, avec -O0, de telle sorte que le code résultant est réellement récursif, la version récursive est littéralement objdump -ds code> j'ai remarqué quelques choses:
Avec l'optimisation -O3, le code ci-dessus optimise complètement l'appel de la fonction. Cependant, il produit toujours du code pour les deux fonctions et n'est pas non plus récursif. P>
Merci pour la perspicacité. J'ai essayé avec -O0 auparavant, mais GCC ne génère pas le code que je m'attendais. Je m'attendais à ce qu'il fonctionnait comme un système où si vous n'avez pas d'opérations en attente, cela réutiliserait l'environnement. En fait, avec -O0 il n'y a pas de moyen d'avoir une fonction de récursivité qui compte pour toujours, elle se terminera toujours par un débordement de pile (défaut de segmentation dans l'affaire CGC).
Premier, Si vous voulez obtenir des mesures significatives, vous devez vous assurer que
que p>
clock_gettime code> semble être la mesure de l'horloge murale, pas
temps d'exécution. Deuxièmement, le temps réel que vous mesurez est le
Heure d'exécution de
printf code>, pas l'heure d'exécution de votre fonction.
Et troisièmement, la première fois que vous appelez
printf code>, ce n'est pas en mémoire, alors il
doit être paginé, impliquant un disque important IO. Inverse de l'ordre
Vous exécutez les tests et les résultats seront également inverse. P>
Il y a une différence dans les courses de la fonction itérative et la fonction récursive et la raison est l'optimisation du compilateur Tout d'abord, le code: p> Les messages précédents étaient corrects que vous devez itération à plusieurs reprises afin d'éviter les interférences d'environnement à l'extérieur. Étant donné que j'ai rejeté votre fonction de différenciation en faveur de J'ai également corrigé un bogue dans la mise en œuvre récursive. Votre déclaration IF-FABS compilation et exécution avec -Foptimize-appels de sodibling code> ajouté par
-O3 code>. p>
Time.h code> La fonction code> la fonction code> sur
TIME_T CODE> Data, depuis sur de nombreuses itérations, quelque chose de plus fin. qu'une seconde n'a pas de sens. De plus, j'ai supprimé les Printfs dans la référence. P>
FABS (XNew-XO) de votre code d'origine
-O3 code>: p>
$ gcc optimization.c -O3 -lrt -o dg
$ ./dg
time_iter: 41 s, time_rec: 24 s, ratio (iter/rec): 1.708333
return values: 0.000487, 0.000487
number of recurs/iters: 1755032704, 1755032704
Merci! C'est une analyse très complète du code .. Réellement plus détaillé que prévu. Cependant, je ne comprends pas ce résultat: Time_iter: 34 s, Time_Rec: 0 s, Ratio (ITER / REC): INF
Il semble que la fonction ne donne pas la bonne réponse car elle semble vraiment suspecte qu'après 10000000 Itérations Cela ne durerait pas au moins 1s tandis que l'autre est 34. Je ne sais pas seulement sonne bizarre ...
Et je ne pouvais pas le reproduire ... Je copie et collé votre code et j'ai compilé avec -O3 et j'ai toujours reçu le ratio 1.
C'est peut-être aussi une question d'environnement. Qu'avez-vous testé? J'ai testé sur Rhel5 avec 5 CPU et 34 Go de mémoire. Cela a peut-être eu un impact sur mes performances améliorées, bien que tout ce qui devrait faire est d'exagérer les avantages d'optimisation. Je me méfiais aussi, alors je définissais deux variables statiques à 0 et ++ code> 'd eux sur chaque recur / interface et les comptes résultants étaient les mêmes (1755032704, si vous souhaitez vérifier; assurez-vous de vérifier; assurez-vous de vérifier corrigez le <= bug). L'incrémentation de la variable statique détruit le gain d'optimisation de la queue: Time_iter: 34 s, Time_Rec: 24 s, ratio (ITER / REC): 1.416667
Il semble certainement une question d'environnement. J'ai testé sur 2 pc à la fois I5 Core et les résultats n'ont pas changé. De vos résultats ressemblent à votre code est en quelque sorte exécuté en parallèle. Est-ce que c'est possible?
Pourquoi exploiterait la récursion de la queue rendre la fonction récursive plus rapidement que celle itérative? La version itérative réutilise les variables comme l'appel récursif de la queue. Pour référence, je reçois également des résultats identiques pour les deux fonctions à l'aide de GCC 4.6.1 sur Ubuntu 11.10 Compilation avec -O3 code>.
Tout "pourquoi est-ce plus rapide?" Les questions doivent être accompagnées de la sortie du code de montage de votre compilateur.
Où est la déclaration de retour pour
decgrad code> pour le cas où le
si code> instruction est faux? Ce code ne devrait pas compiler.
@ user112358132134: Nah, il ne devrait que compiler avec un avertissement, non?
Je ne suis pas convaincu que c'est plus rapide. Vous devez parcourir les deux appels à plusieurs reprises à la moyenne sur la gigue. Vous devez supprimer Printf de la section référencée.
Que obtenez-vous si vous exécutez les tests de synchronisation dans l'autre ordre (premier
Descgrad code>, puis
Descgraditer code>)? Il pourrait être
printf code> initialisation qui prend tout ce temps, comme l'implique Totowtwo.
Avec le renvoi manquant code> corrigé, un nombre décent d'itérations et
printf code> retiré de la référence, les deux fois sont Très similaire .
@Tonyk a juste raison! Merci! Cela résout le mystère, la distorsion a été occasionnée par le printf. Maintenant, les deux implémentations sont à peu près la même qui a du sens! Merci!