Мне кажется, что оптимизация хвостовой рекурсии как на C, так и на C++ отлично сработает, но при отладке я никогда не вижу стека кадров, который указывает на эту оптимизацию. Это хорошо, потому что стек говорит мне, насколько глубока рекурсия. Однако оптимизация тоже была бы неплохой.
Какие-нибудь компиляторы C++ делают эту оптимизацию? Почему? Почему нет?
Как мне сказать компилятору сделать это?
/O2 или /Ox-O2 или -O3Как насчет проверки, сделал ли компилятор это в определенном случае?
Я бы по-прежнему прислушивался к предложениям о том, как определить, оптимизирована ли определенная функция таким образом компилятором (хотя меня обнадеживает то, что Конрад говорит мне принять это)
Всегда можно проверить, делает ли компилятор это вообще, выполнив бесконечную рекурсию и проверив, приводит ли это к бесконечному циклу или переполнению стека (я сделал это с помощью GCC и обнаружил, что -O2 достаточно), но я хочу иметь возможность проверить определенную функцию, которая, как я знаю, все равно завершится. Я хотел бы иметь простой способ проверить это :)
После некоторого тестирования я обнаружил, что деструкторы разрушают возможность такой оптимизации. Иногда может быть полезно изменить область видимости определенных переменных и временных файлов, чтобы убедиться, что они выходят за пределы области видимости до запуска оператора return.
Если какой-либо деструктор необходимо запустить после хвостового вызова, оптимизация хвостового вызова не может быть выполнена.





Все текущие основные компиляторы выполняют оптимизацию хвостового вызова довольно хорошо (и работает уже более десяти лет), даже для взаимно рекурсивных вызовов, например:
int bar(int, int);
int foo(int n, int acc) {
return (n == 0) ? acc : bar(n - 1, acc + 2);
}
int bar(int n, int acc) {
return (n == 0) ? acc : foo(n - 1, acc + 1);
}
Позволить компилятору выполнить оптимизацию просто: просто включите оптимизацию для скорости:
/O2 или /Ox.-O3.Простой способ проверить, выполнил ли компилятор оптимизацию, - это выполнить вызов, который в противном случае привел бы к переполнению стека, или посмотреть на вывод сборки.
В качестве интересного исторического примечания, оптимизация хвостового вызова для C была добавлена в GCC в ходе дипломная работа Марком Пробстом. В дипломной работе описаны некоторые интересные нюансы при реализации. Это стоит прочитать.
@Paul Вопрос в том, какая часть скорости кода ICC вызвана алгоритмической оптимизацией, такой как оптимизация хвостового вызова, а какая - оптимизацией кеша и микрокоманд, которую может сделать только Intel, обладающая глубоким знанием своих процессоров.
gcc имеет более узкую опцию -foptimize-sibling-calls для «оптимизации одноуровневых и конечных рекурсивных вызовов». Эта опция (согласно страницам руководства gcc(1) для версий 4.4, 4.7 и 4.8, предназначенных для различных платформ) включается на уровнях -O2, -O3, -Os.
Кроме того, работа в режиме DEBUG без явного запроса оптимизации НЕ приведет к НИКАКОЙ оптимизации вообще. Вы можете включить PDB для истинного режима выпуска EXE и попробовать выполнить его, но обратите внимание, что отладка в режиме выпуска имеет свои сложности - невидимые / разделенные переменные, объединенные переменные, переменные, выходящие за пределы области в неизвестной / неожиданной области, переменные, которые никогда не входят области и стали истинными константами с адресами на уровне стека, и - ну - слитыми или отсутствующими кадрами стека. Обычно объединенные кадры стека означают, что вызываемый объект встроен, а отсутствующие / объединенные кадры, вероятно, являются хвостовым вызовом.
Большинство компиляторов не оптимизируют отладочную сборку.
Если вы используете VC, попробуйте сборку релиза с включенной информацией PDB - это позволит вам проследить через оптимизированное приложение, и вы, надеюсь, тогда увидите то, что хотите. Обратите внимание, однако, что отладка и отслеживание оптимизированной сборки заставят вас повсюду прыгать, и часто вы не можете напрямую проверять переменные, поскольку они только попадают в регистры или полностью оптимизируются. Это «интересный» опыт ...
попробуйте gcc why -g -O3 и оптимизируйте отладочную сборку. xlC ведет себя так же.
Когда вы говорите «большинство компиляторов»: какие наборы компиляторов вы рассматриваете? Как уже указывалось, существует по крайней мере два компилятора, которые выполняют оптимизацию во время отладочной сборки - и, насколько я знаю, VC тоже это делает (кроме случаев, когда вы, возможно, разрешаете изменение и продолжение).
Как упоминает Грег, компиляторы не будут делать этого в режиме отладки. Это нормально, если отладочная сборка будет медленнее, чем производственная сборка, но они не должны падать чаще: и если вы зависите от оптимизации хвостового вызова, они могут делать именно это. Из-за этого часто лучше переписать хвостовой вызов как обычный цикл. :-(
gcc 4.3.2 полностью встраивает эту функцию (дрянная / тривиальная реализация atoi()) в main(). Уровень оптимизации - -O1. Я замечаю, что если я поиграюсь с ним (даже меняя его с static на extern, хвостовая рекурсия уходит довольно быстро, так что я бы не стал зависеть от этого для правильности программы.
#include <stdio.h>
static int atoi(const char *str, int n)
{
if (str == 0 || *str == 0)
return n;
return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
for (int i = 1; i != argc; ++i)
printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
return 0;
}
Тем не менее, вы можете активировать оптимизацию времени компоновки, и я предполагаю, что тогда даже метод extern может быть встроен.
Странный. Я только что протестировал gcc 4.2.3 (x86, Slackware 12.1) и gcc 4.6.2 (AMD64, Debian wheezy) и с -O1, есть нет встраивания и оптимизация без хвостовой рекурсии. У вас должен быть используйте -O2 для этого (ну, в 4.2.x, который сейчас довольно древний, он все равно не будет встроен). Кстати, также стоит добавить, что gcc может оптимизировать рекурсию, даже если она не является строго хвостовой (например, факториал без аккумулятора).
Помимо очевидного (компиляторы не выполняют такого рода оптимизацию, если вы этого не попросите), существует сложность оптимизации хвостового вызова в C++: деструкторы.
Учитывая что-то вроде:
int fn(int j, int i)
{
if (i <= 0) return j;
Funky cls(j,i);
return fn(j, i-1);
}
Компилятор не может (как правило) оптимизировать этот хвостовой вызов, потому что ему нужно
для вызова деструктора clsпосле рекурсивный вызов возвращается.
Иногда компилятор видит, что деструктор не имеет видимых извне побочных эффектов (поэтому это можно сделать на ранней стадии), но часто это не так.
Особенно распространенная форма этого - когда Funky на самом деле является std::vector или аналогичным.
У меня не работает. Система сообщает мне, что мой голос заблокирован до тех пор, пока ответ не будет отредактирован.
Только что отредактировал ответ (убрал скобки), и теперь я могу отменить свой голос против.
Я считаю, что ICC так и поступит. Насколько мне известно, ICC производит самый быстрый код на рынке.