Какие компиляторы C++ выполняют оптимизацию хвостовой рекурсии, если таковые имеются?

Мне кажется, что оптимизация хвостовой рекурсии как на C, так и на C++ отлично сработает, но при отладке я никогда не вижу стека кадров, который указывает на эту оптимизацию. Это хорошо, потому что стек говорит мне, насколько глубока рекурсия. Однако оптимизация тоже была бы неплохой.

Какие-нибудь компиляторы C++ делают эту оптимизацию? Почему? Почему нет?

Как мне сказать компилятору сделать это?

  • Для MSVC: /O2 или /Ox
  • Для GCC: -O2 или -O3

Как насчет проверки, сделал ли компилятор это в определенном случае?

  • Для MSVC включите вывод PDB, чтобы иметь возможность отслеживать код, затем проверьте код
  • Для GCC ..?

Я бы по-прежнему прислушивался к предложениям о том, как определить, оптимизирована ли определенная функция таким образом компилятором (хотя меня обнадеживает то, что Конрад говорит мне принять это)

Всегда можно проверить, делает ли компилятор это вообще, выполнив бесконечную рекурсию и проверив, приводит ли это к бесконечному циклу или переполнению стека (я сделал это с помощью GCC и обнаружил, что -O2 достаточно), но я хочу иметь возможность проверить определенную функцию, которая, как я знаю, все равно завершится. Я хотел бы иметь простой способ проверить это :)


После некоторого тестирования я обнаружил, что деструкторы разрушают возможность такой оптимизации. Иногда может быть полезно изменить область видимости определенных переменных и временных файлов, чтобы убедиться, что они выходят за пределы области видимости до запуска оператора return.

Если какой-либо деструктор необходимо запустить после хвостового вызова, оптимизация хвостового вызова не может быть выполнена.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
158
0
43 644
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Все текущие основные компиляторы выполняют оптимизацию хвостового вызова довольно хорошо (и работает уже более десяти лет), даже для взаимно рекурсивных вызовов, например:

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);
}

Позволить компилятору выполнить оптимизацию просто: просто включите оптимизацию для скорости:

  • Для MSVC используйте /O2 или /Ox.
  • Для GCC, Clang и ICC используйте -O3.

Простой способ проверить, выполнил ли компилятор оптимизацию, - это выполнить вызов, который в противном случае привел бы к переполнению стека, или посмотреть на вывод сборки.

В качестве интересного исторического примечания, оптимизация хвостового вызова для C была добавлена ​​в GCC в ходе дипломная работа Марком Пробстом. В дипломной работе описаны некоторые интересные нюансы при реализации. Это стоит прочитать.

Я считаю, что ICC так и поступит. Насколько мне известно, ICC производит самый быстрый код на рынке.

Paul Nathan 21.10.2008 08:04

@Paul Вопрос в том, какая часть скорости кода ICC вызвана алгоритмической оптимизацией, такой как оптимизация хвостового вызова, а какая - оптимизацией кеша и микрокоманд, которую может сделать только Intel, обладающая глубоким знанием своих процессоров.

Imagist 28.09.2009 16:14

gcc имеет более узкую опцию -foptimize-sibling-calls для «оптимизации одноуровневых и конечных рекурсивных вызовов». Эта опция (согласно страницам руководства gcc(1) для версий 4.4, 4.7 и 4.8, предназначенных для различных платформ) включается на уровнях -O2, -O3, -Os.

FooF 10.01.2014 10:53

Кроме того, работа в режиме DEBUG без явного запроса оптимизации НЕ приведет к НИКАКОЙ оптимизации вообще. Вы можете включить PDB для истинного режима выпуска EXE и попробовать выполнить его, но обратите внимание, что отладка в режиме выпуска имеет свои сложности - невидимые / разделенные переменные, объединенные переменные, переменные, выходящие за пределы области в неизвестной / неожиданной области, переменные, которые никогда не входят области и стали истинными константами с адресами на уровне стека, и - ну - слитыми или отсутствующими кадрами стека. Обычно объединенные кадры стека означают, что вызываемый объект встроен, а отсутствующие / объединенные кадры, вероятно, являются хвостовым вызовом.

Петър Петров 07.01.2015 12:02

Большинство компиляторов не оптимизируют отладочную сборку.

Если вы используете VC, попробуйте сборку релиза с включенной информацией PDB - это позволит вам проследить через оптимизированное приложение, и вы, надеюсь, тогда увидите то, что хотите. Обратите внимание, однако, что отладка и отслеживание оптимизированной сборки заставят вас повсюду прыгать, и часто вы не можете напрямую проверять переменные, поскольку они только попадают в регистры или полностью оптимизируются. Это «интересный» опыт ...

попробуйте gcc why -g -O3 и оптимизируйте отладочную сборку. xlC ведет себя так же.

g24l 11.01.2015 23:46

Когда вы говорите «большинство компиляторов»: какие наборы компиляторов вы рассматриваете? Как уже указывалось, существует по крайней мере два компилятора, которые выполняют оптимизацию во время отладочной сборки - и, насколько я знаю, VC тоже это делает (кроме случаев, когда вы, возможно, разрешаете изменение и продолжение).

skyking 23.10.2017 14:38

Как упоминает Грег, компиляторы не будут делать этого в режиме отладки. Это нормально, если отладочная сборка будет медленнее, чем производственная сборка, но они не должны падать чаще: и если вы зависите от оптимизации хвостового вызова, они могут делать именно это. Из-за этого часто лучше переписать хвостовой вызов как обычный цикл. :-(

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 может быть встроен.

Konrad Rudolph 01.02.2010 12:52

Странный. Я только что протестировал gcc 4.2.3 (x86, Slackware 12.1) и gcc 4.6.2 (AMD64, Debian wheezy) и с -O1, есть нет встраивания и оптимизация без хвостовой рекурсии. У вас должен быть используйте -O2 для этого (ну, в 4.2.x, который сейчас довольно древний, он все равно не будет встроен). Кстати, также стоит добавить, что gcc может оптимизировать рекурсию, даже если она не является строго хвостовой (например, факториал без аккумулятора).

przemoc 15.01.2012 15:36

Помимо очевидного (компиляторы не выполняют такого рода оптимизацию, если вы этого не попросите), существует сложность оптимизации хвостового вызова в 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 или аналогичным.

У меня не работает. Система сообщает мне, что мой голос заблокирован до тех пор, пока ответ не будет отредактирован.

hmuelner 16.08.2016 00:34

Только что отредактировал ответ (убрал скобки), и теперь я могу отменить свой голос против.

hmuelner 16.08.2016 00:36

Другие вопросы по теме