Question sur « Queue Appel Optimisation » Article
-
11-10-2019 - |
Question
J'ai une question concernant cet article .
Entre ce code
function odds(n, p) {
if(n == 0) {
return 1
} else {
return (n / p) * odds(n - 1, p - 1)
}
}
et ce code
(function(){
var odds1 = function(n, p, acc) {
if(n == 0) {
return acc
} else {
return odds1(n - 1, p - 1, (n / p) * acc)
}
}
odds = function(n, p) {
return odds1(n, p, 1)
}
})()
1) Je suis confus au sujet de combien cela aide. Le deuxième extrait ont simplement un appel de queue qui crée moins de frais généraux, car il calcule ce dont il a besoin avant qu'il se dit à nouveau, ou est-il quelque chose de plus que je suis absent?
Si je comprends bien, l'appel de la queue est toujours pas éliminé, juste optimisé.
2) Et pourquoi il besoin d'être odds
et odds1
de toute façon? On ne sait pas encore pour moi non plus.
La solution
Je suis confus au sujet de combien cela aide. Le deuxième extrait ont simplement un appel de queue qui crée moins de frais généraux, car il calcule ce dont il a besoin avant qu'il se dit à nouveau, ou est-il quelque chose de plus que je suis absent?
Si je comprends bien, l'appel de la queue est toujours pas éliminé, juste optimisé.
Si la fin d'une procédure ressemble à ceci:
push args
call foo
return
Ensuite, le compilateur peut optimiser les lignes juste
jump startOfFoo
L'élimination de tout à fait l'appel de procédure.
Et pourquoi il faut être chances et odds1 de toute façon? On ne sait pas encore pour moi non plus.
Le « contrat » de odds
prévoit seulement deux arguments - le troisième argument est juste un détail de mise en œuvre. Alors vous cacher loin dans une méthode interne, et de présenter un « emballage », comme l'API externe.
On pourrait appeler odds1
quelque chose comme oddsImpl
et il rendrait plus clair, je pense.
Autres conseils
Si vous regardez la pile d'appel puis la première irait comme ceci:
odds(2, 3)
odds(1, 2)
odds(0, 1)
return 1
return 1/2 * 1
return 2/3 * 1/2
alors que le second serait:
odds(2, 3)
odds1(2, 3, 1)
odds1(1, 2, 2/3)
odds1(0, 1, 1/2 * 2/3)
return 1/3
return 1/3
return 1/3
return 1/3
parce que vous renvoyer la valeur de l'appel récursif le compilateur peut optimiser ce facilement:
odds(2, 3)
#discard stackframe
odds1(2, 3, 1)
#discard stackframe
odds1(1, 2, 2/3)
#discard stackframe
odds1(0, 1, 1/3)
return 1/3
La raison d'avoir odds
et odds1
est simplement de fournir la valeur de l'accumulateur initial lorsque tout autre code appelle cette fonction.
L'optimisation de la récursivité de queue est suit comme, dans le premier exemple, puisque vous ne pouvez pas calculer le résultat de la return (n / p) * odds(n - 1, p - 1)
de multiplication jusqu'à ce que vous avez appelé chance (n-1), le interperator doit tenir notre position actuelle dans la mémoire (sur la pile), et ouvrir un nouvel appel à cote.
récursive, qui se produira dans le prochain appel aussi bien, et celle d'après et ainsi de suite. Nous avons donc n opérations en attente au moment où nous arrivons à la fin de notre récursivité et commencer à retourner le valeurset calcul des produits.
Dans le second exemple, étant donné que l'instruction de retour est exécuté return odds1(n - 1, p - 1, (n / p) * acc)
simplement que nous pouvons calculer les arguments de la fonction, et simplement appeler le odds1 (n-1) sans tenir notre position actuelle . c'est là l'optimisation est, parce que maintenant je ne dois me rappeler où je suis à chaque fois que j'ouvre un nouveau cadre sur la pile.
Pensez-y comme références de livres. Imaginez que vous ouvrez un livre de cuisine et allez dans une certaine recette et les ingrédients sont répertoriés comme suit:
- sel
- les ingrédients sur la page suivante
la page suivante a
- poivre
- les ingrédients sur la page suivante
etc. comment voulez-vous dire ce que tous les ingrédients sont? vous devez vous rappeler ce que vous avez vu sur chaque page!
bien que le second exemple est plus comme la liste des ingrédients suivants:
- sel
- oublier, il suffit d'utiliser ce que vous voyez sur la page suivante
la page suivante a:
- sel
- poivre
- oublier, il suffit d'utiliser ce que vous voyez sur la page suivante
etc. au moment où vous atteignez la dernière page (notez que l'analogie est exacte en ce qu'aussi bien prendre la même quantité d'appels de fonction), vous avez tous les ingrédients, sans avoir à « garder en mémoire » ce que vous avez vu sur chaque page, parce qu'il est tout ce qu'il ya sur la dernière page!