LINUX.ORG.RU

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

 


3

4

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

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

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

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

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

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

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

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

Ты имеешь ввиду применить процедуру ADD к регистрам сопроцессора? Но у этой процедуры нет доступа к этим регистрам, так?

именно так. Потому-что данная операция недопустима, из-за разных ТИПОВ.

Почему? Вот у меня в памяти в [AX] лежит вещественное число и в [BX] лежит вещественное число и я делаю ADD [AX], [BX]. Операнды - вещественные числа, очевидно.

с каких хренов они вещественные?! я точно также могу записать вещественное 3.1415926 в int x, и кричать - ТИПОВ НЕТ.

Ну вот смотрю на результат и вижу там

всем пофиг, что ТЫ там видишь. CPU ничего такого не наблюдает.

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

Еще какой. Тебе уже несколько страниц это талдычат: экономия далеко не резиновой памяти стека, как следствие - улучшение cache locality, что особенно полезно для C++-кода, развернутого из темплейтов, ну и отсутствие ненужных RET-ов, каждый из которых - это несколько операций чтения из памяти.

то вы, Михаилы, мне тут доказываете, что CPU это продвинутый компилятор, а то мне доказываете, что RET как в 80х годах на Z80 читает указатель возврата из общей памяти... Кто-то из вас явно что-то недоговаривает...

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

Опять ты про свой высокоуровневый язык. Не интересно. Внутри компилятор это в регистр положит.

я про тот случай, когда компилятор НЕ положит. TCO например... Или RET. С чего ты взял, что CPU адрес возврата обязательно в память суёт, а не в каком-то скрытом регистре хранит?

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

У тебя какое-то свое (и, как всегда, нелепое) определение того, что такое интерпретатор, и что такое компилятор.

Далеко не каждый компилятор C сделает из «i++; i++; i++; i++; ...» что-то более умное. А компилятор в процессоре еще и ограничен размером IC, и basic block размером больше IC он обрабатывать не станет.

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

я про тот случай, когда компилятор НЕ положит.

А я про тот компилятор, который в процессоре.

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

drBatty

какая и над КАКИМИ? Над любыми что-ли? Как понимать результат обработки скажем двух строк?

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

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

а то мне доказываете, что RET как в 80х годах на Z80 читает указатель возврата из общей памяти...

Большинство CPU вообще ни про какие CALL/RET ничего не знает, и потому и не пытаются их оптимизировать. Посмотри, например, как вызовы в ARM сделаны. Компилятор в CPU такой умный только в пределах одного basic block.

Ты не забывай, что мы говорим про ДЛИННУЮ цепочку вызовов (то самое, что так любит генерить C++). Тут тебе никакой оптимизации не будет по определению, и, скорее всего, даже в кэше указателей возврата не останется. Так что цена может быть ОЧЕНЬ высокой.

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

О скорости выполнения речь в общем случае не идёт, ибо «зависит от условий».

Так вообще-то абсолютно любая оптимизация в общем случае может на скорость повлиять в любую сторону. Даже такая банальщина, как loop invariant code motion может и сильно ускорить внутренний цикл, и, наоборот, сильно замедлить (если вычисление этого самого инварианта окажется дешевле, чем введение лишнего регистра для хранения результата).

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

Может, к моменту спецификации что-то изменилось, но (из той же статьи):
«C-- has a very weak type system, recognising only the following types: word1, word2, word4, word8, float4, float8»- насколько я понимаю, это значит, что bitsk - разные типы для разных значений k.

Странно, надо будет на досуге прояснить этот вопрос.

И даже в той цитате, что ты привел: «There are just two types in C--» это уже 2 типа :)

Первоначальное сообщение было: «А как же word, byte? :)». Я говорил исключительно о размерности типов.

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

ну например команда imul обрабатывает старший бит аргументов как знак

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

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

Ты бредишь чтоли? процессор выполняет бинарник. Не код компилируемый компилятором и не код ассемблера. Так что типы ЯП и типы ассемблера тут никакого отношения не имеют.

есть.

ну так покажи их. Вот у меня в памяти лежит 00000000 - это какой тип?

А задаются/проверяются они на этапе компиляции или написания программы на ассемблере.

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

Скорее есть «специальный тип» НЁХ. Его по разному называют. Считай его «результатом» деления на ноль. С этим «типом» нельзя никаких операций производить.

а не про результат деления на ноль. Я про аргумент деления. Этот аргумент должен иметь тип «не ноль». Так?

ну в CPU всё точно также, я могу записать что-то в N байт в eax

а с чего ты взял что после этого в eax лежит целое, а не число с плавающей точкой?

если ты объявил x целым, но записал туда ASCIIZ «XYZ»

Если я объявил тип целым, то я не могу записать туда ASCIIZ «XYZ», т.к. это литерал другого типа. Если же записал - значит тип х не целое, а юнион целого и строки.

значит ты выбрал не ту команду. ADD AX,DX складывает два целых 16и битных числа.

Нет, это процедура, которая некоторым образом обрабатывает два 16-битных вектора. Если эти 16-битные вектора неким определенным образом интерпретировать как целые числа, то результат можно будет интерпретировать как их сумму (с некоторыми оговорками) . но это не значит что я не могу например интерпретировать эти вектора как булевы переменные, тогда результат будет xor'ом. Причем интерпретация зависит только от программиста. итипы в голове программиста. А в ЦПУ нет никакой интерпретации - он не знает ничего о чилсах. Он даже не знает, что ADD называется ADD.

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

Большинство CPU вообще ни про какие CALL/RET ничего не знает

ну x86 традиционно знает. И я так полагаю, что верхние байты стека там уже много лет лежат рядом с ALU. push/pop/call/ret оптимизированы там ОЧЕНЬ хорошо, в частности call уже давно работает не медленнее jmp.

Ты не забывай, что мы говорим про ДЛИННУЮ цепочку вызовов (то самое, что так любит генерить C++).

ну не такая уж и длинная там цепочка. Оно бы всё быстро и чудестно отрабатывало, если-бы не невозможность inline, но это всё совсем не из-за TCO, а из-за дизайна C++, и его любви переходить по дважды косвенному адресу (сначала получаем из объекта указатель на VT, потом из этой VT переходим на нужный метод). Очевидно, что такое никак не соптимизировать, и всякие TCO помогут тут слабо...

Так что цена может быть ОЧЕНЬ высокой.

ну ладно, есть - пусть будет, жалко что-ли?

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

именно так. Потому-что данная операция недопустима, из-за разных ТИПОВ.

При чем тут типы? просто операция не имет доступа к соответствующим регистрам. Она например и к информации на винте доступа не имеет.

с каких хренов они вещественные?!

А почему нет? Какие основания у меня есть интерпретировать их как целые? Точно те же, что и основания интерпретировать их как вещественные.

я точно также могу записать вещественное 3.1415926 в int x, и кричать - ТИПОВ НЕТ.

В сишке - да, можешь. В сишке и впрямь типов нет, там кривой костыль вместо типов. В нормальных языках ты 3.1415926 в int никогда не запишешь. Тем наличие типов и отличается от их отсутствия.

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

Над любыми что-ли?

Да, над любыми.

Как понимать результат обработки скажем двух строк?

Как тебе удобнее, так и понимай. Тебя в этом плане никто не ограничивает.

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

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

Уехали они давно. Ты забыл, что цепочка вызовов ДЛИННАЯ?

Очевидно, что такое никак не соптимизировать, и всякие TCO помогут тут слабо...

TCO очень сильно помогает. Десятки процентов ускорения.

ну ладно, есть - пусть будет, жалко что-ли?

Жалко - это сотни, а в вырожденных случаях и тысячи тактов простоя.

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

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

с точки зрения IMUL тип ЗНАКОВОЕ ЦЕЛОЕ ЧИСЛО. Что ты там подсунул, и как ты результат интерпретировал - только твои слова. Твоё any существует только в твоей голове.

imul работает НЕ одинаково:

1. для целых знаковых она работает ПРАВИЛЬНО

2. для любых других - НЕ ПРАВИЛЬНО

Если тебя это не волнует, то какой смысл спорить?

Ты бредишь чтоли? процессор выполняет бинарник. Не код компилируемый компилятором и не код ассемблера. Так что типы ЯП и типы ассемблера тут никакого отношения не имеют.

ты не поверишь, но в бинарнике тоже есть указания на типы. Есть указания на размер операндов, есть указания на их тип. Типы ЯП транслируются в типы бинарника, если ты для 86-32 написал double, то в бинарнике будет указание хранить в стеке FPU.

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

есть. просто ты про них не знаешь.

ну так покажи их. Вот у меня в памяти лежит 00000000 - это какой тип?

причём тут «память»? ты ещё файл возьми для примера... Покажи мне код, я тебе расскажу, как этот КОД интерпретирует твои нули.

а не про результат деления на ноль. Я про аргумент деления. Этот аргумент должен иметь тип «не ноль». Так?

не так. «не ноль» это значение. Его _может_ иметь любой числовой тип. В том числе и аргумент для операции деления. А в качестве результата тогда будет НЁХ.

а с чего ты взял что после этого в eax лежит целое, а не число с плавающей точкой?

потому-что для eax не предусмотрено операций с FP, и их делать можно только костыльно. Предусмотрены только целочисленные операции. Такой вот у eax ТИП, смирись. Этот хитрый тип EAX совмещает в себе сразу 3 типа(из других ЯП) :

1. знаковое целое

2. беззнаковое целое

3. битовая карта на 32 бита.

Конечно можно взгромоздить костыли, и написать эмулятор FPU используя только этот универсальный тип (в нём я ещё забыл упомянуть о нескольких флагах). Да, калькулятор из текстового редактора sed тоже можно сделать, как и морской бой на brainfuck'е. И что?

Если я объявил тип целым, то я не могу записать туда ASCIIZ «XYZ», т.к. это литерал другого типа. Если же записал - значит тип х не целое, а юнион целого и строки.

можешь. Можешь

1. записать в память XYZ

2. считать из памяти int по тому же адресу.

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

ты можешь интерпретировать что угодно, и как угодно. А операцию ADD AX,DX из 8086 придумали и реализовали для сложения целых чисел.

ЗЫЖ ты уже в маразм впадаешь - вернись в IRL и подумай - «как я могу интерпретировать XYZ, и что из этого получится, если я это XYZ буду использовать не по назначению?».

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

Значит неважно аргументы какого типа ей приходят - с точки зрения процедуры тип аргументов один единственный Any.

Ну что за бред? На этом уровне вообще нет такого понятия, как тип данных. Есть машинные слова разного размера.

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

Ну что за бред? На этом уровне вообще нет такого понятия, как тип данных. Есть машинные слова разного размера.

Я про это и говорю. Тип только один (что равносильно отсутствию типов).

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

Тип только один (что равносильно отсутствию типов).

Нет. Ты бредишь. Отсутствует само понятие «типов». Это никак не сводится к идиотскому утверждению «тип только один».

Если имеешь что возразить, то соблаговоли сначала строго формализовать эту твою странную систему типов, где «тип только один». Опиши семантику. Будет смешно!

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

При чем тут типы? просто операция не имет доступа к соответствующим регистрам.

блин, просто удивительное совпадение: одна из самых востребованных операций, почему-то не имеет доступа к целому блоку регистров... Случайность? Тупость инженеришек интела? Острая нехватка битов?

А почему нет? Какие основания у меня есть интерпретировать их как целые? Точно те же, что и основания интерпретировать их как вещественные.

иди, даташиты почитай...

В сишке - да, можешь. В сишке и впрямь типов нет, там кривой костыль вместо типов. В нормальных языках ты 3.1415926 в int никогда не запишешь. Тем наличие типов и отличается от их отсутствия.

запишу. Через память. Или у тебя там память тоже тип имеет? Интересно какой?

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

с точки зрения IMUL тип ЗНАКОВОЕ ЦЕЛОЕ ЧИСЛО.

С точки зрения IMUL ему приходит битовый вектор определенной длины. Больше IMUL ничего о том что ему пришло не знает, ни о числах ни о целых ни о знаках. Ни о чем.

2. для любых других - НЕ ПРАВИЛЬНО

С чего бы это неправильно?

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

Приведи мне указания на их тип. Ну вот как мне записать ADD AX BX, но только указав, что в AX лежит float?

есть. просто ты про них не знаешь.

Докажи.

причём тут «память»? ты ещё файл возьми для примера... Покажи мне код, я тебе расскажу, как этот КОД интерпретирует твои нули.

Код не умеет интерпретировать, интерпретировать может только человек, ебанько ты эдакое. С точки зрения кода это все битовые вектора, чтобы ты ему не подсунул.

не так. «не ноль» это значение. Его _может_ иметь любой числовой тип.

Так, давай вернемся к началу. Дай определение типу и обоснуй, что «не ноль» не удовлетворяет этому определению.

потому-что для eax не предусмотрено операций с FP, и их делать можно только костыльно. Предусмотрены только целочисленные операции. Такой вот у eax ТИП, смирись. Этот хитрый тип EAX совмещает в себе сразу 3 типа(из других ЯП) :

То, что это операции над FP или над целыми числами это _твоя личная интерпретация_. Для процессора это операции над битовыми векторами.

1. знаковое целое
2. беззнаковое целое
3. битовая карта на 32 бита.

Замечательно, то, что оно может быть битовой картой ты хоть согласился. но теперь давай, посмотрим, можем ли мы объяснить функционирование процессора, оставив только тип «битовых карт»? Да, можем, все прекрасно работает и все хорошо. Так зачем множить сущности? С тем же успехоможно придумать типы розового единорога и макаронного монстра, но зачем? Что это нам даст?

можешь. Можешь

Нет, не могу.

1. записать в память XYZ
2. считать из памяти int по тому же адресу.

Язык, в котором можно свободно записывать в нетипизированную память и читать оттуда динамически типизирован (то есть в этом языке нету типов). Если язык статически типизирован (типы есть), то ты ничего такого сделать не можешь и в int строку не запишешь.

А операцию ADD AX,DX из 8086 придумали и реализовали для сложения целых чисел.

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

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

drBatty

для любых других - НЕ ПРАВИЛЬНО

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

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

Уехали они давно. Ты забыл, что цепочка вызовов ДЛИННАЯ?

что-то не так с кодом ИМХО. С исходным.

Жалко - это сотни, а в вырожденных случаях и тысячи тактов простоя.

СОВСЕМ не так...

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

блин, просто удивительное совпадение: одна из самых востребованных операций, почему-то не имеет доступа к целому блоку регистров... Случайность? Тупость инженеришек интела? Острая нехватка битов?

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

Вообще смысл спора не ясен. Вот есть тегированная архитектура - это как раз пример когда CPU знает о типах, то есть бинарник типизирован. А нетегированная архитектура - бестиповая by-design.

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

запишу. Через память. Или у тебя там память тоже тип имеет? Интересно какой?

прикинь, да, есть языки с типизированными поинтерами без приведения типов. И если у тебя поинтер на int то ты туда и положить можешь только int.

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

ну в доках пишут, что не над любыми.

Пруф? Где там в доках написано, что на вход нужно принимать только битовые векторы такого-то вида?

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

Я говорил исключительно о размерности типов.

Насколько я понял из твоей цитаты, вместе с bits1 таки есть boolean.

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

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

да? сколько будет сумма векторов 001 и 001? По твоему 010? А что это за «вектора» у тебя такие?

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

Вообще смысл спора не ясен. Вот есть тегированная архитектура - это как раз пример когда CPU знает о типах, то есть бинарник типизирован. А нетегированная архитектура - бестиповая by-design.

это «узколобость» называется - когда есть только чёрное, и только белое. IRL нет ни того, ни другого, а кроме серого есть ещё огромное количество других цветов. На самом деле, x86 он тоже «с нестрогой типизацией», т.е. типы там есть, но их в некоторых пределах можно (взаимо)заменять. Тот же EAX можно использовать по разному, но есть вещи, для которых его использовать нельзя в принципе (т.е. хранить конечно можно там любые 32 бита, но ИСПОЛЬЗОВАТЬ их можно только строго оговорённым типом).

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

да? сколько будет сумма векторов 001 и 001? По твоему 010? А что это за «вектора» у тебя такие?

Что еще за «сумма векторов»? Сперва определи эту операцию, потом спрашивай.

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

это «узколобость» называется - когда есть только чёрное, и только белое.

Блять, бабушка не может быть наполовину дедушкой.

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

прикинь, да, есть языки с типизированными поинтерами без приведения типов. И если у тебя поинтер на int то ты туда и положить можешь только int.

C3 * 2^24 + C2 * 2^16 + C1 * 8 + C0 = INT уже там не работает?

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

но ИСПОЛЬЗОВАТЬ их можно только строго оговорённым типом

С чего вдруг? Как хочешь так и используй.

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

бабушка не может быть наполовину дедушкой.

ну а вот C и процессор может быть нестрого типизирован. Смирись.

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

С чего вдруг? Как хочешь так и используй.

вот ты уже засунул два вещественных числа в EAX, вот теперь рассказывай, как их умножать. Жду...

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

ну а вот C и процессор может быть нестрого типизирован. Смирись.

ну то есть он нетипизирован, а значит типов там нет. О чем и шла речь.

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

что-то не так с кодом ИМХО. С исходным.

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

СОВСЕМ не так...

Але, ты совсем заврался, да? Стоимость одного cache miss знаешь?

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

C3 * 2^24 + C2 * 2^16 + C1 * 8 + C0 = INT

Если ты скажешь, что написал, то я скажу работает или нет.

способ, как из 4х char'ов в 8 бит сделать один int в 32 бита.

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

вот ты уже засунул два вещественных числа в EAX, вот теперь рассказывай, как их умножать. Жду...

А как их умножали до появления FPU?

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

способ, как из 4х char'ов в 8 бит сделать один int в 32 бита.

И как? 2^24 - это нихуя не чар. Так что ты тут делаешь инт из двух интов и 4 чаров.

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

Погоди, автор этого поста ни про какие «сложения векторов» не говорил. Это ты про них серанул, вот и объясняй собственнопридуманные термины.

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

Так, остановились все быстренько, и написали каждый свое определение понятия «тип». А то вы тут какой-то совсем уж нелепой херней страдаете.

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

А как их умножали до появления FPU?

точно также, как я запихал 4 char'а в один int, хотя ты, школьник, сказал, что это «невозможно».

Вот формула:

C3 * 2^24 + C2 * 2^16 + C1 * 8 + C0 = INT
обдумай, как её применить для умножения INT'ов, если можно множить только CHAR'ы?

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

И как? 2^24 - это не чар. Так что ты тут делаешь инт из двух интов и 4 чаров.

ну и что? по условию у меня есть int и char, и надо запихать char'ы в int. Что тебя пугает?

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

точно также, как я запихал 4 char'а в один int

но ты не запихал 4 чара в один инт.

Вот формула:

Тут нету никакого запихивания 4 чаров в один инт. Тут сумма 4 интов. (точнее может и не четырех - все зависит от порядка выполнения плюсов).

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