а более реалистичный пример можно? с одно стороны метапрограммирование — это прекрасное упражнение для мозгов, но с другой — я не понимаю, где оно может дать серьезные бенефиты.
у template metaprogramming есть на мой взгляд лишь один плюс перед подходом основанном на кодогенерации: шаблоны являются частью языка. На мой взгляд этот плюс убивается мозговзрывающей сложностью более или менее серьезного шаблона.
Постусловия выражают состояния «окружающего мира» на момент выполнения подпрограммы.
Мне кажется лучше сказать на момент завершения подпрограммы.
Кроме того, наличие постусловия в подпрограмме гарантирует ее завершение (т.е. не будет бесконечного цикла, например).
Интересно, что в классической логике Хоара наличие постусловия никак не связано собственно с её завершением. Формулируется два понятия:
Программа с предусловие P и постусловием Q корректна, если начиная работать на входных данных удовлетворяющих P, она либо не завершается, либо завершается в состоянии удовлетворяющем Q.
Программа с предусловие P и постусловием Q тотально корректна, если начиная работать на входных данных удовлетворяющих P она завершается в состоянии удовлетворяющем Q.
Вообще завершение или не завершение программы больше зависит от предусловия, чем от постусловия.
1. а если не известна? её же насчитать надо. а насчитывать для языка типа JS её можно ой как долго.
2. ну понятное дело, что совместимость на объектном уровне. главное в том, что проблема для классических компиляторов решается. а для JIT её вообще не существует.
3. Там — это где? И что даже нет ограничений на размер тела цикла?
был сделан простой анролл цикла. Unrolling представляет собой дублирование тела цикла N раз:
Опять же таже проблема, что и с инлайном. Как определить, где делать unroll и где не делать? Не тривиальный вопрос на самом деле.
Но на самом деле unroll здесь не причем. Трассирующий JIT по своей структуре завязан собственно на оптимизацию горячих путей, т.е. путей по которым много ходят. А это как раз циклы. Поэтому циклы и оптимизируются.
Легко выкидывать проверки в C++, который позволяет портить память.
В любом типо-безопасном (type safe) языке, что бы выкинуть проверку типа компилятору надо основательно глобально подумать. Представьте себе функцию на языке Java:
void f(BaseClass o) {
((ChildClass)o).foo();
}
Компилятору что бы выкинуть проверку надо доказать, что любое o является экземпляром ChildClass и никак иначе.
А теперь представьте, что типов вообще нет. Что есть замыкания (closures) и ассоциативные массивы. Это сущий ад для классического глобального анализа. Конечно, корифеи собаку на это съели, когда оптимизировали программы на Smalltalk подобных языках. Но трассирующий JIT предоставляет элегантное и что самое главное дешевое (т.е. быстрое и потребляющее мало памяти) решение данной проблемы.
Естественно вся ответственность за типы, а точнее их возможное несоответствие ложится на программиста :)
Компилятор для type safe языка не имеет права такого делать. Ответственность исключительно на компиляторе и только на нём.
Ну эт обычно называется RTTI skip — выбросили проверку типов там где она не нужна…
RTTI skip = Run-time type information skip довольно странно звучащее название. Переводится как «Пропуск Информации о Типах Времени Исполнения». Кажется, вы его сами придумали.
Даже в статическом языке что бы выкинуть проверку типа компилятору надо основательно глобально подумать. Представьте себе функцию
А вы попробуйте
Естественно вся ответственность за типы, а точнее их возможное несоответствие ложится на программиста :)
Пожалуйста прежде чем комменитровать что-либо походите по ссылкам и ознакомьтесь с первоисточником.
Там все эти оптимизации сделаны иначе, чем они делаются в классическом компиляторе для языка со статической типизацией (а-ля Intel C Compiler). Я позволю себе прокоментировать ваши комментарии с учетом этой разницы.
Это сейчас практически не проблема — памяти ощутимо много
Классический подход: Проблема с открытой подстановкой (инлайном) в том, что она в общем случае увеличивает давление на регистры. А регистров никогда не бывает много =). В общем случае задача инлайна представляет собой разновидность задачи о ранце, которая как известно NP-полна. Поэтому что бы определить какой метод в какой подставлять обычно используют разного вида эвристики и явные флаги (always inline). Реже profile guided optimization: делается тестовый прогон и собирается информация о горячих методах, далее эвристики учитывают эту информацию. Как показывают исследования наибольший прирост производительности даёт как раз использование одновременно и профиля и эвристик на основе статического анализа.
Подход трассирующего JIT: Мы сразу получаем информацию о горячих точках и начинаем записывать трассу именно с горячей точки. Поэтому получается что трассирующий JIT фактически «на халяву» учитывает профиль программы.
Компилятор НЕ умеет инлайнить функции из соседнего модуля, из системных библиотек, а так из динамических библиотек
Классический подход: Проблема межмодульного инлайна сейчас начинает решаться, компиляторы () обзаводятся оптимизациями на этапе компоновки. Плюс есть ряд компиляторов, в которых межмодульный инлайн поддерживался изначально (i.e. Excelsior JET).
Подход трассирующего JIT: Запись трассы ведётся во время исполнения и не прекращается во время вызова. Поэтому никакой проблемы межмодульного инлайна нет.
Но как источник серьезных знаний о компиляторах я бы не стал эту книгу рассматривать. Существуют более глубокие и аккуратно построенные курсы (например, Мучник). А вообще информацию о научных новинках можно и нужно получать из научных статей =)
if (isnumber(z) && isnumber(y))
x = sumNumbers(z, y)
else if (isstring(z) && isnumber(y))
x = concat(z, tostring(y))
…
Вообщем мотню переходов. Трассирующий же JIT в породит специализированный фрагмент кода с _фиксированными_ типами z и y. И всего двумя проверками на тип, если эти проверки проваляться то произвойдёт деоптимизация, т.е. выход из оптимизированного кода обратно в интерпретатор. Какие типы фиксировать определяется статически: если какой-то цикл постоянно исполнятся так, что в его теле z является строкой, а y — числом, то компиляция случится именно в таких предположениях.
Далее трассирующий JIT может записывать инструкции между вызовами: это позволяет автоматически делать открытую подстановку и специализацию косвенного вызова, что опять же положительно сказывается на производительности.
ВЫ дали ссылку на работу Франца, а вы её сами-то читали? Франц продвигает идею трассирующего JITа, т.е. такого JITa: находит вход в «горячее место» (заголовок цикла), когда исполнение входит в это горячее место он начинает записывать все _исполняющиеся_ инструкции в одну (почти) линейную последовательность. Затем эта линейная последовательность оптимизируется. Собственно это и есть ключевое отличие от классического JITа. В языках с динамической типизацией не получается просто вывести тип произвольной переменной, поэтому классический JIT мало что может (он же еще и ограничен по времени, так что ему глобально думать о типах некогда). Трассирующий JIT же записывает последовательность _специализированных_ инструкций.
Потому что для любого человека эта фраза тавтология. Потому трудно представить себе человека, который скажет фразу «резюме совпадает с человеком» и даже представить это себе трудно.
Программисту знающему один язык (кстати это должен быть язык, в котором полиморфизм «не в ходу»), совсем не трудно осознать примитивные концепции любого другого языка (если это не восьмидесятилетняя бабушка со спекшимися мозгами, которая когда-то 60 назад втыкала штекеры в гнёзда и называла это программированием).
Программисту знающему один язык, легко прочитать статью Карделли и осознать полиморфизм. Ну а после перейти к конкретным примерам, для тех языков, которые он знает или собирается изучать.
но к вычислению константных выражений это всё отношения не имеет.
я вот более чем уверен, что препроцессор не занимается вычислением константных выражений.
у template metaprogramming есть на мой взгляд лишь один плюс перед подходом основанном на кодогенерации: шаблоны являются частью языка. На мой взгляд этот плюс убивается мозговзрывающей сложностью более или менее серьезного шаблона.
Какой такой «прекомпилятор»?
Мне кажется лучше сказать на момент завершения подпрограммы.
Интересно, что в классической логике Хоара наличие постусловия никак не связано собственно с её завершением. Формулируется два понятия:
Программа с предусловие P и постусловием Q корректна, если начиная работать на входных данных удовлетворяющих P, она либо не завершается, либо завершается в состоянии удовлетворяющем Q.
Программа с предусловие P и постусловием Q тотально корректна, если начиная работать на входных данных удовлетворяющих P она завершается в состоянии удовлетворяющем Q.
Вообще завершение или не завершение программы больше зависит от предусловия, чем от постусловия.
я о другом видимо подумал, вас не понял.
2. ну понятное дело, что совместимость на объектном уровне. главное в том, что проблема для классических компиляторов решается. а для JIT её вообще не существует.
3. Там — это где? И что даже нет ограничений на размер тела цикла?
Я там прокомментировал его «описание».
Опять же таже проблема, что и с инлайном. Как определить, где делать unroll и где не делать? Не тривиальный вопрос на самом деле.
Но на самом деле unroll здесь не причем. Трассирующий JIT по своей структуре завязан собственно на оптимизацию горячих путей, т.е. путей по которым много ходят. А это как раз циклы. Поэтому циклы и оптимизируются.
Продолжим.
Легко выкидывать проверки в C++, который позволяет портить память.
В любом типо-безопасном (type safe) языке, что бы выкинуть проверку типа компилятору надо основательно глобально подумать. Представьте себе функцию на языке Java:
void f(BaseClass o) {
((ChildClass)o).foo();
}
Компилятору что бы выкинуть проверку надо доказать, что любое o является экземпляром ChildClass и никак иначе.
А теперь представьте, что типов вообще нет. Что есть замыкания (closures) и ассоциативные массивы. Это сущий ад для классического глобального анализа. Конечно, корифеи собаку на это съели, когда оптимизировали программы на Smalltalk подобных языках. Но трассирующий JIT предоставляет элегантное и что самое главное дешевое (т.е. быстрое и потребляющее мало памяти) решение данной проблемы.
Компилятор для type safe языка не имеет права такого делать. Ответственность исключительно на компиляторе и только на нём.
RTTI skip = Run-time type information skip довольно странно звучащее название. Переводится как «Пропуск Информации о Типах Времени Исполнения». Кажется, вы его сами придумали.
Даже в статическом языке что бы выкинуть проверку типа компилятору надо основательно глобально подумать. Представьте себе функцию
А вы попробуйте
Естественно вся ответственность за типы, а точнее их возможное несоответствие ложится на программиста :)
Там все эти оптимизации сделаны иначе, чем они делаются в классическом компиляторе для языка со статической типизацией (а-ля Intel C Compiler). Я позволю себе прокоментировать ваши комментарии с учетом этой разницы.
Классический подход: Проблема с открытой подстановкой (инлайном) в том, что она в общем случае увеличивает давление на регистры. А регистров никогда не бывает много =). В общем случае задача инлайна представляет собой разновидность задачи о ранце, которая как известно NP-полна. Поэтому что бы определить какой метод в какой подставлять обычно используют разного вида эвристики и явные флаги (always inline). Реже profile guided optimization: делается тестовый прогон и собирается информация о горячих методах, далее эвристики учитывают эту информацию. Как показывают исследования наибольший прирост производительности даёт как раз использование одновременно и профиля и эвристик на основе статического анализа.
Подход трассирующего JIT: Мы сразу получаем информацию о горячих точках и начинаем записывать трассу именно с горячей точки. Поэтому получается что трассирующий JIT фактически «на халяву» учитывает профиль программы.
Классический подход: Проблема межмодульного инлайна сейчас начинает решаться, компиляторы () обзаводятся оптимизациями на этапе компоновки. Плюс есть ряд компиляторов, в которых межмодульный инлайн поддерживался изначально (i.e. Excelsior JET).
Подход трассирующего JIT: Запись трассы ведётся во время исполнения и не прекращается во время вызова. Поэтому никакой проблемы межмодульного инлайна нет.
Но как источник серьезных знаний о компиляторах я бы не стал эту книгу рассматривать. Существуют более глубокие и аккуратно построенные курсы (например, Мучник). А вообще информацию о научных новинках можно и нужно получать из научных статей =)
Скажем если у нас есть код
x = z + y
То обычный JIT скорее всего породит код вида
if (isnumber(z) && isnumber(y))
x = sumNumbers(z, y)
else if (isstring(z) && isnumber(y))
x = concat(z, tostring(y))
…
Вообщем мотню переходов. Трассирующий же JIT в породит специализированный фрагмент кода с _фиксированными_ типами z и y. И всего двумя проверками на тип, если эти проверки проваляться то произвойдёт деоптимизация, т.е. выход из оптимизированного кода обратно в интерпретатор. Какие типы фиксировать определяется статически: если какой-то цикл постоянно исполнятся так, что в его теле z является строкой, а y — числом, то компиляция случится именно в таких предположениях.
Далее трассирующий JIT может записывать инструкции между вызовами: это позволяет автоматически делать открытую подстановку и специализацию косвенного вызова, что опять же положительно сказывается на производительности.
Спасибо за внимание =)
Скажем если у нас есть запись
x = z + y.
Там используется трассирующий
Программисту знающему один язык (кстати это должен быть язык, в котором полиморфизм «не в ходу»), совсем не трудно осознать примитивные концепции любого другого языка (если это не восьмидесятилетняя бабушка со спекшимися мозгами, которая когда-то 60 назад втыкала штекеры в гнёзда и называла это программированием).
Программисту знающему один язык, легко прочитать статью Карделли и осознать полиморфизм. Ну а после перейти к конкретным примерам, для тех языков, которые он знает или собирается изучать.