Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Аппаратный стек тогда бы содержал только один указатель на фрейм и адрес возврата.
ЭФ>Либо даже адрес возврата был бы тоже во фрейме, например первым.
ЭФ>Да, это дополнительная косвенность, но кого это вообще волнует в наши времена быстрых процессоров?
ЭФ>https://stackoverflow.com/questions/26741925/is-frame-in-jvm-heap-allocated-or-stack-allocated
Более-менее всех.
Смотрите, стек можно организовывать одним из трёх способов:
1. Полностью непрерывная область памяти, положить/снять со стека эквивалентно икременту/декременту stack pointer-а.
2. Связный список фреймов: каждый фрейм выделяется в куче, ссылается на предыдущий фрейм.
3. Комбинация подходов: связный список сегментов.
Второй способ применяется только в нишевых скриптовых движках, где быстродействие стоит на последнем месте.
Потому, что
каждый вызов — это выделение в куче. Даже если мы в цикле вызываем один и тот же метод. Такой подход выжирает кучу космическими темпами, и провоцирует невероятно частую сборку мусора.
Ну, и собственно стоимость вызова резко увеличивается из-за подготовки фрейма — его же надо не просто выделить, но и проинициализировать указатель на предыдущий фрейм.
Плюс к этому размывается локальность кэша — на каждой итерации мы лезем к новым адресам за параметрами и переменными.
третий способ применялся на разных этапах развития многих языков, в частности Go и Rust. Практика показала, что никаких преимуществ этот подход не даёт, зато регулярно стреляет в пересечение границы сегмента кодом в плотном цикле. Это приводит к резкой просадке производительности на ровном месте.