LINUX.ORG.RU

О хвостовой рекурсии

 


3

4

Или с чем ее едят и как ее готовят? Набрел на статью в Википедии, ознакомился, сделал сравнительный замер времени выполнения для приведенного там примера. Результаты впечатлили, особенно если учесть, что тестировал я это для Scala с включенной оптимизацией.пиляторы

Собственно вопросов два.

1. Хотелось бы получше разобраться в устройстве хвосто-рекурсивных функций и их вызовов, дабы объяснить человеку, далекому от этой темы - в чем там магия?

2. Какие еще популярные ЯП и компиляторы поддерживают оптимизацию данного вида рекурсии?

Всем спасибо.

Ответ на: комментарий от kaathewise

Можно пойти еще дальше, пусть у нас есть функция max, которая принимает две константные ссылки, и возвращает максимум. У компилятора опять нет способа узнать в общем случае, на что ссылаются ее параметры (это может зависеть от логики внешней функции). Поэтому в этом случае он опять не сможет в ТСО, даже если все переменные во внешней функции - инты.

kaathewise
()
Ответ на: комментарий от kaathewise

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

kaathewise
()
Ответ на: комментарий от quasimoto

Относительно утверждения о том, что отсутствует понятие «тип» - откроем, например, интеловский мануал и почитаем (vol 1. ch. 4) про пять (шесть и даже семь или восемь - всё как слова разных размеров) фундаментальных типов

Это все интерпретации типа, а не тип.

Любой ассемблер (тот же gas) все эти многочисленные особенности статически проверяет

Каким образом? Такая проверка была бы алгоритмически неразрешимой задачей, так что не надо врать.

anonymous
()
Ответ на: комментарий от anonymous

Вы утверждаете, что типов нет, но никак не объясняете наличие разных операция для разных ... ТИПОВ

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

anonymous
()
Ответ на: комментарий от quasimoto

Даже без модели и если не нравится говорить «тип» (интеловский мануал вот не брезгует), то всё равно нужно что-то проговаривать вместо слова «тип», чтобы не путать мух с котлетами. Например, «битовый-вектор-32» и «битовый-вектор-64» это две разные «вещи», «битовый-вектор-64» <: «битовый-вектор-32» и «битовый-вектор-32» != «битовый-вектор-64», так что «битовый-вектор-32» можно поднять в «битовый-вектор-64», но «битовый-вектор-64» нельзя опустить в «битовый-вектор-32» (нет биекции), можно только стереть в него

А зачем останавливаться на векторах определенной длины? Можно объявить, что для каждого значения существует тип, к которому принадлежит это значение и только оно, а кроме того тип может быть произвольным объединением других типов. Важне же чтобы эти типы были фиксированы статически - но этого то мы и не наблюдаем! Любая операция процессора «статически» может работать на битовом веткоре _любой_ длины. А то что он в реультате имеет длину не ту, которую предполагает логика операции - так это можно определить только в рантайме.

anonymous
()
Ответ на: комментарий от quasimoto

В принципе, так и есть, по крайней мере, _строгие_ ограничения только такого рода. Есть ограничение на типы dst/src (например, нельзя прибавить в память кусок из памяти), есть различия между вещами разного размера (add(b, w, l, q), fadd*, padd*, add*s, add*d, ...), есть разница в физическом расположении регистров и в логике разных инструкций. Всё остальное уже чисто умозрительно.

Дело в том, что когда мы вызываем одну операцию для разных регистров - то на самом деле это _разные_ операции. То, что в ассемблере она называется одинаково - ну это просто синтаксический сахарок для человека. ADD AX, BX - одна операция, ADD AX, CX - совершенно другая. Соответственно, операции, которая применяет сложение с плавающей точкой на регистрах общего назначения просто нет. Не то что там она плохо типизирована - а нет ее.

anonymous
()
Ответ на: комментарий от kaathewise

А именно, если в функции есть локальные объекты

А локальные объекты создаются в куче. На стеке хранится только указатель - который при ТСО гарантированно не нужен и его можно убрать.

anonymous
()
Ответ на: комментарий от anonymous

Может быть, я неудачно выразился, но я имел в виду локальные статические объекты, для которых будет автоматически вызван деструктор.

kaathewise
()
Ответ на: комментарий от anonymous

Это все интерпретации типа, а не тип.

Если следовать тамошней терминологии, то «фундаментальные типы» (byte, word, ...) это как раз всё то что не интерпретация, а интерпретаций уже много - вплоть до битовых полей.

Каким образом?

Я про проверку типов dst/src и размеров слов, типа addl %xmm0,%xmm1, addl (%eax),(%ebx), movq %eax,%eax и т.п. Много разных вещей.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Важне же чтобы эти типы были фиксированы статически - но этого то мы и не наблюдаем!

Про статичность / строгость речи вроде не шло, а просто про наличие понятия различных типов.

Любая операция процессора «статически» может работать на битовом веткоре _любой_ длины.

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

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

ADD AX, BX - одна операция, ADD AX, CX - совершенно другая.

Верно, поэтому я там выписал add(b, w, l, q), fadd*, padd*, add*s, add*d, ... А теперь зададимся вопросом - чем отличаются addb и addl, например? Или addl %eax,%eax и addl (%eax),(%eax). Типами операндов. Разные опкоды работают со словами разного размера:

  4004df:	05 ab ef cd ab       	add    $0xabcdefab,%eax
  4004e4:	04 ab                	add    $0xab,%al
quasimoto ★★★★
()

тред не читай сразу отвечай

1. если чел знает рекурсию - то магия хвостовой - отказ от храниения уже ненужного окружения и как следствие замена рекурсии циклом. если чел не знает магии рекурсии - то начинать с индукции и сведения к простейшему . ветка конечная и ветка приближающая на шаг к конечной.

2. тысячи их.

qulinxao ★★☆
()
Ответ на: комментарий от drBatty

1. железный стек всё равно быстрее.

ну так где бенчмарк, сравнивающий железный стек (бугага!) с чем-нибудь типа

int* a; 
int head;

inline void push(int x) { a[++head]=x; }
inline int pop() { return a[--head]; }

int test(int N) {
    int s=0;
    a = malloc(....);
    for(i=0; i<N; ++i) {
        for(int j=0; j<1000000; ++j)
            push(j^i);
        ++head;
        for(int j=0; j<1000000; ++j)
            s+=pop();
    }
    return s;
}

ты сказал — ты и должен доказывать свои слова

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от qnikst

судя по коду, который ты привёл, - не помнишь.

там должно быть N*(N+1)/2, а не -1

но вот с чем соглашусь — что с батти надо требовать бенчмарк, а пока он не сделал его — напоминать ему при каждом его ответе

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от anonymous

Идиот. Где тут пуш и поп, придурь?!?

Начало блока - это push локальных переменных, конец блока - pop, я написал же.

anonymous
()
Ответ на: комментарий от quasimoto

Если следовать тамошней терминологии, то «фундаментальные типы» (byte, word, ...)

Это уже интерпретация - процессору же без разницы word перед тобой или несколько byte там лежит.

Я про проверку типов dst/src и размеров слов, типа addl %xmm0,%xmm1, addl (%eax),(%ebx), movq %eax,%eax

И что, оно проверяет какого размера аргумент, лежащих в eax? Каким образом? Я и гвоорю - это задача алгоритмически неразрешимая. То, что мы всунули в eax, зависит от control flow.

anonymous
()
Ответ на: комментарий от quasimoto

А теперь зададимся вопросом - чем отличаются addb и addl, например?

Всем. Это просто разные операции.

Типами операндов. Разные опкоды работают со словами разного размера:

С чего ты взял? Они все работают с произвольными битовыми векторами. Что хочешь то и суй этой операции в регистр с которым она работает.

anonymous
()
Ответ на: комментарий от quasimoto

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

То, что таким образом «неудобно писать» на предмет обсуждения никак не влияет. Важно что так писать принципиально _можно_.

Про статичность / строгость речи вроде не шло, а просто про наличие понятия различных типов.

Если говорить о «динамических типах», то они есть везде (каждому множеству значений - свой тип). Тут и говорить не о чем. Так что речь шла именно о статических типах.

anonymous
()
Ответ на: комментарий от quasimoto

Чем разные?

Всем. Это просто разные операции.Одинаковые операции - это «add ax, bx» и «add ax, bx» например.

anonymous
()
Ответ на: комментарий от anonymous

Это уже интерпретация - процессору же без разницы word перед тобой или несколько byte там лежит.

Нет разницы 8-битовое или 32-битовое слово следует за опкодом 04?

addl %xmm0,%xmm1, addl (%eax),(%ebx), movq %eax,%eax

И что, оно проверяет какого размера аргумент, лежащих в eax?

И потом

Важно что так писать принципиально _можно_.

Все три примера - примеры которые gas не скомпилирует. Потому что в xmm* - 128-битное слово, в eax - 32-битное и процессор не умеет add на памяти минуя регистры.

То есть игнорировать различия в тех статических типах которые есть нельзя.

Я и гвоорю - это задача алгоритмически неразрешимая.

Речь не про то. Он (ассемблер как программа), конечно, не проверяет что в eax (32) лежит именно байт (8) и нули (24), это вообще не нужно.

Это просто разные операции.

Да, сложения, но для операндов разных типов :)

С чего ты взял? Они все работают с произвольными битовыми векторами.

С вполне фиксированными, addb - 8-битными, addl - 32-битными.

Что хочешь то и суй этой операции в регистр с которым она работает.

То есть можно совать 32-битные слова в 8-битные регистры? Или речь про, например, 8-битное слово в *l/h регистры и *b операции? Как это отменяет наличие слов других размеров (типов) и наличия других (по типам) операций?

Ну и как бы - я засунул в al что хочу и сделал addb что хочу,al, процессор при этом посчитал что это числа и (OMG!) сложил их! И кто эти «8-битовые векторы» после этого как не числа типа «целые числа по модулю 256» (интерпретация, да)? Никто не запрещает, конечно, взять «как бы плавающее число» и разобрать его по байтам, может это именно то что нужно.

Так что речь шла именно о статических типах.

Надо всё-таки учесть, что определённые статические требования тоже есть, если их не соблюдать при генерации машкода всё будет падать с illegal hardware instruction. Да и сама система опкодов крайне статична по своей природе.

Это просто разные операции.

Это один опкод который занимается сложением 16-битных регистров, то какие регистры складывать он решает по значению своего «суффикса» типа «байт» (и его значение - тоже не любой байт, а то illegal hardware instruction).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

addb - 8-битными, addl - 32-битными.

Такая аналогия:

                                        void addb(char d, char* x) { *x += d; }
                                        void addl(int d, int* x) { *x += d; }

   movb $2,%al                          char x = 2;
   movl $2,%ebx                         int y = 2;
   addb $2,%al                          addb(2, &x);
   addl $2,%ebx                         addl(2, &y);
;; addl $2,%al                       // addl(2, &x);
;; addb $2,%ebx                      // addb(2, &y);
quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Начало блока - это push локальных переменных, конец блока - pop, я написал же.

ИДИОТ. Какой push? Какой pop? Переменные адресуются по их именам, дебил. И-М-Е-Н-А-М. Никакой семантики стека тут и близко нет. Ни в каком виде.

anonymous
()
Ответ на: комментарий от quasimoto

Все три примера - примеры которые gas не скомпилирует. Потому что в xmm* - 128-битное слово, в eax - 32-битное и процессор не умеет add на памяти минуя регистры.

Так это просто попытка вызвать операцию, которой нет. Типы тут не при чем.

То есть игнорировать различия в тех статических типах которые есть нельзя.

Но там нету никаких различий в типах, там вообще нету типов.

Речь не про то. Он (ассемблер как программа), конечно, не проверяет что в eax (32) лежит именно байт

О чем и речь.

То есть можно совать 32-битные слова в 8-битные регистры?

Конечно.

Как это отменяет наличие слов других размеров (типов) и наличия других (по типам) операций?

Так это не другие операции «по типам», это просто _другие операции_. То есть вообще другие. Ну как плюс и минус.

Ну и как бы - я засунул в al что хочу и сделал addb что хочу,al, процессор при этом посчитал что это числа и (OMG!) сложил их!

Он не ситал, что это числа и не складывал их. Он ничего про числа не знает. Он выполнил операцию на битовых векторах а то, что в некоторой интерпретации некоторого программиста это имеет нечто общее со сложение - ну так это другое делою

Это один опкод который занимается сложением 16-битных регистров, то какие регистры складывать он решает по значению своего «суффикса» типа «байт» (и его значение - тоже не любой байт, а то illegal hardware instruction).

Нет, это просто разные операции.

anonymous
()
Ответ на: комментарий от anonymous

Какой push? Какой pop?

Обычный Push и Pop.

Переменные адресуются по их именам, дебил. И-М-Е-Н-А-М.

А в стеке - по относительному адресу от верхушки. Что полностью идентично именам и лексическим скопам, так-то.

Никакой семантики стека тут и близко нет. Ни в каком виде.

Есть, конечно. Еще раз - любое «имя» - это смещение от верхушки стека. Как только появляется переменная в текущем скопе - это push, как только убирается переменная из текущего скопа - это pop. Т.о. любой код с регистрами тривиально изоморфен стековому.

anonymous
()
Ответ на: комментарий от anonymous

То есть можно совать 32-битные слова в 8-битные регистры?

Конечно.

Посмотреть бы код сего чуда.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Еще раз, отродье наркоманское, ты вводишь лишнюю сущность. Никакого стека в семантике регистровой машины нет. Ты ее сам додумываешь себе, уродец.

anonymous
()
Ответ на: комментарий от anonymous

Так есть же. Например, сложение целых чисел, сложение чисел с плавающей точкой и пр. Просто нет проверок на соответствие типу. Процессор доверяет входным данным и интерпретирует байтики, например, как целое число.

anonymous
()
Ответ на: комментарий от anonymous

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

Ну вот нафейхоа такой кривой взгляд и лишние сущности? Проверок нет, а «интерпретация» есть? Может вы ещё схемотехнически набросаете сей процесс?

yyk ★★★★★
()
Ответ на: комментарий от yyk

Это означает, что у процессора есть, скажем, операция xxx на битовых векторах, которая из 00001110 и 00111001 делает 01000111, а что это значит, процессор не знает, это интерпретирует программист, и никаких проверок входных данных процессор не делает, в этот битовый вектор можно записать то, что ты считаешь интом, даблом, или чем угодно, процессору это не важно.

kaathewise
()
Ответ на: комментарий от yyk

И, самое главное, нет никакой возможности понять, к какому «типу» относится значение битового вектора, эта информация нигде не записана.

kaathewise
()
Ответ на: комментарий от kaathewise

нет никакой возможности понять, к какому «типу» относится значение битового вектора

К типу «битовый вектор» :)

quasimoto ★★★★
()
Ответ на: комментарий от yyk

Какие лишние сущности? Вы хоть раз открывали спеки по командам процессоров? Теоретики, блин. Тип - это и есть то, как мы интерпретируем байтики. А со множествами и предикатами идите в Haskell.

anonymous
()
Ответ на: комментарий от kaathewise

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

anonymous
()
Ответ на: комментарий от kaathewise

С чего вдруг это стало самым главным?

anonymous
()
Ответ на: комментарий от kaathewise

это интерпретирует программист

А выше анон написал, что интерпретирует процессор ;)

yyk ★★★★★
()
Ответ на: комментарий от anonymous

мы интерпретируем байтики

Так всё-таки мы интерпретируем, а не процессор? Хамовитый практик даже читать не умеет? Дислексия?

yyk ★★★★★
()
Ответ на: комментарий от anonymous

то значит есть и типы

У программиста - есть. У процессора - нет. Нет типов данных. С натяжкой можно сказать, что есть «типы операций». Но это всё скатывается (вернее уже давно скатилось) в терминологический спор - не интересно.

yyk ★★★★★
()
Ответ на: комментарий от quasimoto

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

qnikst ★★★★★
()
Ответ на: комментарий от yyk

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

anonymous
()
Ответ на: комментарий от yyk

для тех, кто не в курсе сюжета, всё началось с того, что drBatty начал утверждать, что в его процессоре есть инты, даблы и указатели, а вот лямбд, нет, поэтому всё ФП говно.

qnikst ★★★★★
()
Ответ на: комментарий от anonymous

Интерпретирует процессор для того, чтобы правильно вычислить.

Маааать... man схемотехника и перестань трахать мой мозг! Нет в процессоре никакого «процесса интерпретации» - операция сложения по установленным правилам (в которых НЕТ никакой интерпретации!) обработает «входные данные» и поместит результат в оговоренное место. Всё!

Целые числа рассматриваются таким-то образом

никаким образом они не рассматриваются!

имеют такую-то длину, хранятся в таких-то регистрах и складываются по таким-то правилам

это - верно. Но процесса «рассмотрения» нет!

yyk ★★★★★
()
Ответ на: комментарий от qnikst

всё началось с того, что drBatty начал утверждать

да и йух с ним

yyk ★★★★★
()
Ответ на: комментарий от qnikst

в железе нет никаких проверок

А флаги, исключения, прерывания и защищённый режим?

и никаких типов

Прямая ссылка без СМС и регистрации! ;)

Я тут называю типами слова разных размеров. Они есть операционно. Вот такая штука будет интерпретировать машинный код в котором зашиты слова разных размеров (например, 04 _ab_ 05 _ab ab ab ab_) и который обращается к словам в памяти разных размеров, пусть даже в конечном итоге упрощая всё в RISC-подобные микро-операции. ALU / SSE будут при этом редукционно поставлять результат последней стадии цикла, то есть давать фундаментальным типам интерпретации.

Ну а все остальные типы это уже денотации.

quasimoto ★★★★
()
Ответ на: комментарий от yyk

«Рассмотрение» - заложено в саму операцию для опеделенного типа. Вызовешь операцию не для того «типа» - получишь кривой результат(т.к. байтики будут не верно интерпретированы). Что тут непонятного?

Откройти доки по процам, блин, откройте описание MMIX Кнута. Не увидите там описания типов? Что же тогда такое целое число, число с плавающей точкой, битовый вектор, битовая матрица(раз уж мы о MMIX)? Это не типы? Что это?

anonymous
()
Ответ на: комментарий от yyk

Нет в процессоре никакого «процесса интерпретации» - операция сложения по установленным правилам (в которых НЕТ никакой интерпретации!) обработает «входные данные» и поместит результат в оговоренное место. Всё!

man микрокоды. Работу конвейера вполне можно назвать интерпретацией.

imtw
()
Ответ на: комментарий от anonymous

Вызовешь операцию не для того «типа» - получишь кривой результат(т.к. байтики будут не верно интерпретированы). Что тут непонятного?

Ну давай, покажи пример сложения «не для того типа», где процессором «байтики будут не верно интерпретированы». Или может это всё-же ты как-то не так будешь «интерполировать» байтики, а не процессор?

yyk ★★★★★
()
Ответ на: комментарий от yyk

В смысле? Отсутствие контроля типов не означает отсутствия типов. Вызвал операцию не для того типа - получил неверный результат - ССЗБ.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.