Tytuł pytania może być trochę dziwny, ale chodzi o to, że o ile wiem, nic nie przemawia przeciwko optymalizacji wywołań ogonowych. Jednak podczas przeglądania projektów open source natknąłem się już na kilka funkcji, które aktywnie próbują powstrzymać kompilator przed optymalizacją wywołań ogonowych, na przykład implementację CFRunLoopRef, która jest pełna takich hacków . Na przykład:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (func) {
func(observer, activity, info);
}
getpid(); // thwart tail-call optimization
}
Bardzo chciałbym wiedzieć, dlaczego wydaje się to tak ważne, i czy są jakieś przypadki, w których ja jako normalny programista też powinienem pamiętać? Na przykład. czy są typowe pułapki związane z optymalizacją wywołań ogonowych?
getpid()
nie jest używana, czy nie można jej usunąć przez poinformowany optymalizator (ponieważgetpid
jest to funkcja, o której wiadomo, że nie ma skutków ubocznych), a zatem zezwala kompilatorowi na wykonanie optymalizacji wywołań końcowych? Wydaje się, że to naprawdę delikatny mechanizm.Odpowiedzi:
Domyślam się, że ma to na celu zapewnienie, że
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
znajduje się w śladzie stosu do celów debugowania. To ma__attribute__((no inline))
co potwierdza ten pomysł.Jeśli zauważysz, ta funkcja po prostu przechodzi i odbija się od innej funkcji, więc jest to forma trampoliny, o której myślę, że istnieje z tak rozwlekłą nazwą, aby pomóc w debugowaniu. Byłoby to szczególnie pomocne, biorąc pod uwagę, że funkcja wywołuje wskaźnik funkcji, który został zarejestrowany z innego miejsca i dlatego ta funkcja może nie mieć dostępnych symboli debugowania.
Zwróć także uwagę na inne podobnie nazwane funkcje, które robią podobne rzeczy - naprawdę wygląda na to, że pomaga w zobaczeniu, co się stało ze śladu wstecznego. Należy pamiętać, że jest to podstawowy kod systemu Mac OS X, który będzie widoczny w raportach o awariach i przykładowych raportach dotyczących procesów.
źródło
__attribute__((noinline))
. Myślę, że jesteś tutaj na miejscu.__CFRunLoopDoObservers
której na pewno pojawia się w śladzie stosu ...To tylko przypuszczenie, ale być może w celu uniknięcia nieskończonej pętli w porównaniu z bombardowaniem z błędem przepełnienia stosu.
Ponieważ omawiana metoda nie umieszcza niczego na stosie, wydaje się możliwe, że optymalizacja rekurencji wywołań ogonowych wygeneruje kod, który wprowadziłby nieskończoną pętlę w przeciwieństwie do niezoptymalizowanego kodu, który umieściłby adres zwrotny na stosie które w końcu uległyby przepełnieniu w przypadku niewłaściwego użycia.
Jedyna inna myśl, jaką mam, jest związana z zachowaniem wywołań na stosie do debugowania i drukowania śledzenia stosu.
źródło
Jednym z potencjalnych powodów jest ułatwienie debugowania i profilowania (przy całkowitym koszcie posiadania, nadrzędna ramka stosu znika, co utrudnia zrozumienie śladów stosu).
źródło