LINUX.ORG.RU

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

 


3

4

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

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

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

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

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

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

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

но ты не запихивал никаких чаров в инт, ты просто сложил 4 инта и получил инт. Не понимаю, что ты хотел показать этой формулой?

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

Я полгаю под типом:

1. множество, к котормоу принадлежит значение. В этом случае чтобы выделить тип, надо уметь определить его по значению - ЦПУ этого не умеет, значит в этом смысле типов там нет.

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

3. абстрактные метки, которые асосциируются с термами - но в логике работы процессора нету никаких таких абстрактных меток, а значит и нету типов в этм смысле.

Значит с точки зрения процессоров никаких типов нет.

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

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

говорил, я отмотаю, мне не тяжело:

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

А я говорил про ADD из 8086, которая битовые вектора никогда не складывала (для этого нужна XOR оттуда же). Битовом вектором называется массив, у которого каждый эл-т размером в бит.

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

то, что EAX можно ИСПОЛЬЗОВАТЬ как вещественное число.

Можно, раньше все использовали. И что дальше?

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

говорил, я отмотаю, мне не тяжело:

Ну и где он говорил про «сложение битовых векторов»? Он скзаал что это некая операция над битовыми векторами, про сложение высрал ты.

Битовом вектором называется массив, у которого каждый эл-т размером в бит.

То есть содержимое eax - битовые вектора (массивы битов).

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

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

я полагаю, что тип - это определение множества операций над неким множеством аргументов. Например для множества «целые» определены операции «сложения» и т.д. Для множества «вещественные» тоже есть «сложение», но ДРУГОЕ. И в CPU целое нельзя сложить как вещественное. Следовательно тип - есть. Даже если кто думает, что в EAX лежит вещественное (имеет право!), он не сможет сложить EAX так, что-бы получить правильный вещественный ответ. Потому как тип EAX не вещественный.

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

И в CPU целое нельзя сложить как вещественное.

Ну как же нельзя? положил его в FPU-регистр и складываешь. Если бы было нельзя - никто бы о наличии типов и не спорил.

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

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

в твоём ЯП char разве не преобразуется в INT?

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

в твоём ЯП char разве не преобразуется в INT?

А у тебя там в каком-то месте чары преобразуются в int? Уверен?

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

1. множество, к котормоу принадлежит значение. В этом случае чтобы выделить тип, надо уметь определить его по значению - ЦПУ этого не умеет, значит в этом смысле типов там нет.

ЗАЧЕМ выделять тип? К тому-же, по значению 0 принципиально невозможно определить тип, ибо такое значение может быть практически у любого типа.

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

«провести операцию» означает в т.ч. получить верный результат. Если ты считал 2*2, и получил 666, то ты - не умеешь умножать. Или у тебя какие-то неведомые науке цифры. Процессор получает верный результат исключительно при правильности типов. Если тип неверный, операция не произведена, точнее произведена неправильно - программу в /dev/null, быдлокодеру пить ъад.

3. абстрактные метки, которые асосциируются с термами - но в логике работы процессора нету никаких таких абстрактных меток, а значит и нету типов в этм смысле.

завязывай с травой.

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

Можно, раньше все использовали. И что дальше?

школьник, учись. Можешь ради фана помножить пару double без FPU и SSE, может поймёшь что-нибудь... А то скучно...

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

Ну и где он говорил про «сложение битовых векторов»? Он скзаал что это некая операция над битовыми векторами, про сложение высрал ты.

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

То есть содержимое eax - битовые вектора (массивы битов).

да. с EAX можно работать и как с массивом битов. Есть полный набор операций.

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

Ну как же нельзя? положил его в FPU-регистр и складываешь. Если бы было нельзя - никто бы о наличии типов и не спорил.

а есть такой ЯП, в котором я не смогу положить целое в дробное?

Кстати, в x86 это как раз и нельзя. Спор с того и начался, что мой компилятор в стек стал срать целыми, что-бы помножить на FPU. Напрямую свернул-бы в TCO...

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

компилятор тоже НЕ может ничего определить. Ему сказали - «это целое», он считает это целым. Как и процессор.

это не правда даже для такого языка, как Си

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

ЗАЧЕМ выделять тип? К тому-же, по значению 0 принципиально невозможно определить тип, ибо такое значение может быть практически у любого типа.

Что значит зачем? Если тип - это множество значений, то мы всегда можем построить предикат, который скажет принадлежит значение типу Х или нет просто перебрав значения из диапазона.

«провести операцию» означает в т.ч. получить верный результат.

Да, именно.

Если ты считал 2*2, и получил 666, то ты - не умеешь умножать.

но у процессора нету операции *. У него есть imul, например, но imul это не операция над числами - это операция над битовыми векторами. И какой бы ты битовый вектор не поставил - она дает корерктный результат.

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

школьник, учись. Можешь ради фана помножить пару double без FPU и SSE, может поймёшь что-нибудь... А то скучно...

Раньше все умножали. Так в чем проблема?

Я не понимаю, ты пытаешься мне доказать, что такое умножение невозможно или что?

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

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

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

да. с EAX можно работать и как с массивом битов. Есть полный набор операций.

Замечательно. Так какой смысл множить сущности?

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

а есть такой ЯП, в котором я не смогу положить целое в дробное?

Ну конечно! Любой язык с типами.

Кстати, в x86 это как раз и нельзя. Спор с того и начался, что мой компилятор в стек стал срать целыми, что-бы помножить на FPU. Напрямую свернул-бы в TCO...

Тебе уже нцать раз объяснили, что ТСО похуй на регистры, ТСО проводится тогда, когда ВСЕ лежит на стеке и на регистрах еще НИЧЕГО не лежит. С точки зрения ТСО никаких регистров нет. Но до тебя так и не доходит, еблан ты эдакий. А то, что gcc не кладет целые в FPU не говорит о том, что этого нельзя делать.

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

да. я бинарник смотрел.

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

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

ТСО проводится тогда, когда ВСЕ лежит на стеке

Вообще-то тогда и стека никакого нет, как правило (см. GIMPLE, LLVM - там абстрактные регистры, а не стек).

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

я полагаю, что тип - это определение множества операций над неким множеством аргументов

То есть, абстрактная херовина. Множества, херожества - где это все в CPU? Нет там этого.

И в CPU целое нельзя сложить как вещественное.

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

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

drBatty

он, блин отвечал на пост про сложение. точнее даже про умножение

С дуба рухнул? Я тебе говорю, что практически любая инструкция процессора (назовём её xyz или ololo для абстрактности) работает с векторами битов. На одинаковых входных векторах (и одинаковом состоянии для некоторых инструкций) выдаёт одинаковый результат. Возможно, есть инструкция, которая при вызове xyz 2 2 выдаст 666, от этого входные и выходные векторы битов не примут какой-то особенный тип, процессору пофиг, а если тебе не пофиг, то сам думай, что ты суёшь и что тебе дают в ответ. Процессор проделал свою правильную работу по превращению 2 и 2 в 666. Смысл же сего действа исключительно на твоей совести.

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

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

Я так же суну в стек флоат, а потом сделаю ret, процу пофиг, типа-то нет, понимаешь? С точки зрения процессора любые входные данные - это всего лишь вектора битов, которые нужно преобразовать при помощи указанных инструкций.

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

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

http://en.wikipedia.org/wiki/Domain_theory, можно начинать смеяться :)

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

Вообще-то тогда и стека никакого нет, как правило (см. GIMPLE, LLVM - там абстрактные регистры, а не стек).

Абстрактные регистры - это и есть стек.

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

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

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

Ну над GPR/Memory у нас один набор инструкций, над XMM - другой. Есть инструкции которые гоняют данные туда-сюда, но в итоге (псевдокод, но должно быть понятно, что это значит):

cvtsi2sd: bits[128]{xmm.hi, xmm.lo(_ -> float[64])}, bits[32]{gpr|mem}(int[32])
add: bits[32]{gpr|mem}(int[32] -> int[32]), bits[32]{gpr|mem|code}(int[32])

можно сделать add eax, _; cvtsi2sd xmm0, eax, но нельзя cvtsi2sd xmm0, eax; add xmm0, _ - нет композиции, у add и addss разные типы.

То есть уже не однородная архитектура получается.

Или

movzbl: bits[32]{gpr|mem}(int[32] -> int[8]), bits[32]{gpr|mem}(int[32])

с одной тут как было (32) слово, так и есть, но с другой - eax после выполнения movzbl eax, _ всегда будет удовлетворять неким специальным свойствам которым произвольный eax не удовлетворяет (если трактовать типы как предикаты это будет сменой типа).

итовый веткор он битовый веткор и есть.

8, 16, 32, 64, 80, 128, 256, 512 - это один и тот же битовый вектор или разные?

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

компилятор тоже НЕ может ничего определить. Ему сказали - «это целое», он считает это целым. Как и процессор.

это не правда даже для такого языка, как Си

обоснуешь, или как всегда?

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

Что значит зачем? Если тип - это множество значений, то мы всегда можем построить предикат, который скажет принадлежит значение типу Х или нет просто перебрав значения из диапазона.

одно и тоже значение может принадлежать разным типам. например 0.

но у процессора нету операции *. У него есть imul, например, но imul это не операция над числами - это операция над битовыми векторами. И какой бы ты битовый вектор не поставил - она дает корерктный результат.

ага. «корерктный»

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

Раньше все умножали. Так в чем проблема?

проблема в том, что ты знаешь, что умножали, но не знаешь КАК. Вот и узнай.

Я не понимаю, ты пытаешься мне доказать, что такое умножение невозможно или что?

для тебя похоже невозможно...

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

Я про компилятор говорю, при чем тут бинарник? В бинарнике типов нет

есть. только ты про это не знаешь.

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

То есть, абстрактная херовина. Множества, херожества - где это все в CPU? Нет там этого.

есть. над любой сущностью в CPU имеется своё множество допустимых операций.

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

и что? в твоём компьютере такой CPU? вот как будет такой - тогда и поговорим.

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

Процессор проделал свою правильную работу по превращению 2 и 2 в 666. Смысл же сего действа исключительно на твоей совести.

вот только НЕТ такой операции. Есть операция УМНОЖЕНИЕ ЦЕЛЫХ, которая из 2 и 2 делает 4. А операция ololo исключительно в твоих фантазиях существует.

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

Абстрактные регистры - это и есть стек.

http://en.wikipedia.org/wiki/Stack_machine

a stack machine is a real or emulated computer that uses a pushdown stack rather than individual machine registers to evaluate each sub-expression in the program. A stack computer is programmed with a reverse Polish notation instruction set.

The common alternative to stack machines are register machines, in which each instruction explicitly names the specific registers to use for operand and result values.

Регистровые vs стековые IR это же целый срач :)

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

Я так же суну в стек флоат, а потом сделаю ret, процу пофиг, типа-то нет, понимаешь? С точки зрения процессора любые входные данные - это всего лишь вектора битов, которые нужно преобразовать при помощи указанных инструкций.

с точки зрения компилятора - всё точно также. если ты скажешь, что 2 и 2 - целые, то умножение даст тебе ЦЕЛОЕ 4. Как и в процессоре. Если ты считаешь, что 2 и 2 это на самом деле float, то в результате ты получишь НЁХ.

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

Это ты знаешь, что перемножаешь 2 и 2, а проц просто проводит операцию над векторами бит, пусть даже это адреса памяти или куски строк, процессору всё равно, чем являются эти вектора. Над любым вектором он может провести любую свою операцию, а значит типов с его точки зрения нет.

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

http://en.wikipedia.org/wiki/Domain_theory, можно начинать смеяться :)

Посмеюсь, когда ты это сможешь применить к CPU. Вот возьми, в качестве формальной спецификации, Verilog-исходники любого простого CPU (Tiger, например), и примени эту модель.

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

Посмеюсь, когда ты это сможешь применить к CPU

Отмотаю немного назад:

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

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

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

Если спор был именно про язык CPU, то «один тип» iff «один вид слова и полная гомогенность архитектуры».

Относительно утверждения о том, что отсутствует понятие «тип» - откроем, например, интеловский мануал и почитаем (vol 1. ch. 4) про пять (шесть и даже семь или восемь - всё как слова разных размеров) фундаментальных типов и про многочисленные типы-интерпретации, ну и про то как типизированы разные инструкции (vol. 2, ch. 3-4). Любой ассемблер (тот же gas) все эти многочисленные особенности статически проверяет (так что addl %xmm0,%xmm1, addl (%eax),(%ebx) и т.п. не пишутся).

Вот если взять абстрактный CPU в вакууме без сопроцессоров, GPU и с единственным словом некоторого размера (а ещё RISC и без регистров и кешей, тупо с одним уровнем реально плоской памяти), то у нас будут только значения кодируемые данным словом, формально один тип, который нет смысла явно фиксировать, так что вообще без типов. Но нынешние x86 со своим наслоением технологий разных лет сложнее.

Вот возьми, в качестве формальной спецификации, Verilog-исходники любого простого CPU

Лучше ты представь что-то вроде LLVM IR для максимально упрощённой машины, так что от http://llvm.org/docs/LangRef.html#typesystem будет оставаться всё меньше и меньше пока не останется просто слово одного вида. Это, видимо, имелось в виду под «один тип». Поинт, конечно, в том, что в реальности это не так.

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

Untyped это то же самое, что typed с одним рефлексивным типом (в некоторой модели).

Не так. В данном случае не untyped, а вообще никакая модель типов принципиально неприменима.

Лучше ты представь что-то вроде LLVM IR для максимально упрощённой машины

IR как раз таки строго типизированное представление. Но мы то не про ассемблер говорим, не про IR, а про реальное железо.

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

В «компутерах» тип - это интерпретация, а не множество в математическом смысле, по той простой причине, что не предикатом(и даже не перечислением) определяется(хотя может таким способом описываться). Одно и то же представление(набор байтиков) можно интерпретировать по разному, но это не отменяет типы(интерпретации), а как раз указывает на их наличие. Вы утверждаете, что типов нет, но никак не объясняете наличие разных операция для разных ... ТИПОВ =) Сам подумайте, что несете.

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

Относительно утверждения о том, что отсутствует понятие «тип» - откроем, например, интеловский мануал и почитаем (vol 1. ch. 4) про пять (шесть и даже семь или восемь - всё как слова разных размеров) фундаментальных типов и про многочисленные типы-интерпретации, ну и про то как типизированы разные инструкции (vol. 2, ch. 3-4). Любой ассемблер (тот же gas) все эти многочисленные особенности статически проверяет (так что addl %xmm0,%xmm1, addl (%eax),(%ebx) и т.п. не пишутся).

В документацию не лез - лень. Но я вижу _только_ ограничения в командах на указание тех или иных регистров. Если ограничения по регистрам и есть типы...

А вообще, IMHO - великая глупость пытаться «переносить» типы из ЯП в бинарный код для процессора

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

В данном случае не untyped

Разработчики TAL называют классический AL (как язык CPU) untyped (предполагая как раз упрощённый CPU в вакууме).

а вообще никакая модель типов принципиально неприменима.

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

То есть архитектура оснащена не просто (legacy) набором слов разного размера, но и такого рода наборами операций, позволяющими интерпретировать «фундаментальные типы» как какие-то более специфичные «вещи». Если брать FPU/SSE/GPU, то оно даже там существовать будет в замкнутом состоянии независимо от канонiчного ядра. Ну а память с кэшами - просто шина (потому что к ней инструкции в большей степени всеядны, нежели к регистрам).

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

Но я вижу _только_ ограничения в командах на указание тех или иных регистров.

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

великая глупость пытаться «переносить» типы из ЯП в бинарный код для процессора

Главное чтобы от типов там остались их гарантии, сами богатые типы, понятное дело, пропадают.

quasimoto ★★★★
()

drBatty написал много фигни, но он упомянул один важный момент, который, кажется, никто, включая него, не заметил. А именно, если в функции есть локальные объекты, то гцц не может в принципе применить TCO, ибо не может уничтожать объекты, пока не отработает вся функция, т.к. вызываемые могут неочевидным образом полагаться на существование этого объекта. Мне кажется, это общая проблема для языков без гц.

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

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

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

Или еще такой пример: функция f имеет локальный массив, а последний ее вызов, это вызов функции g, которая получает указатели на начало и конец массива и печатает, например, максимум из элементов. Отследить это компилятор тоже не сможет, поэтому оптимизация фцнкции с локальным массивом тоже недопустима.

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