Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
Я имею в виду следующее (на примере Delphi). Предположим, я пишу библиотеку или exe-файл, которому на вход подаётся Delphi-юнит (паскалевский код), и моя программа его немного модифицирует. Среда разработки показывает в двух вкладках два кода: исходный юнит и юнит, модифицированный моим прекомпилятором. При запуске программы компилируется второй код, и естественно отладчик имеет дело с ним.
Вот пример. Есть простой приём оптимизации циклов – размножение. Пусть у меня есть код:
qfor6 i:= 0 to count-1 do
values[i] := values[i]* values[i];
Мой прекомпилятор видит qfor6 и преобразует этот код таким образом:
Intervalscount := count div 6;
itersininervalscount:=intervalscount*6;
curiter:=0;
repeat
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
values[curiter] := values[curiter]*values[curiter];
inc(curiter);
until curiter>=itersininervalscount;
for i:=itersininervalscount to count-1 do
values[i] := values[i]* values[i];
(По ходу вопрос – насколько можно ещё оптимизировать этот код, или это уже предел?)
Ещё можно добавить вторую опцию для прекомпилятора: превратить qfor6 в обычный for. Это нужно для отладки, чтобы проще было разбираться с кодом.
У таких прекомпиляторов нашлось бы масса применений; каждый программист стал бы писать свой для своих задач, по сути совершенствуя исходный ЯП. И на rsdn все бы выкладывали свои прекомпиляторы в целях фаллометрии)
Вот ещё пример применения таких прекомпиляторов: инлайновый функции. Я попробовал освоить инлайновые функции в Delphi XE 8, и обнаружил что они совершенно неудобные: инлайновая функция не может обратиться к переменной в процедуре уровнем выше, или наоборот вызвать собственную вложенную функцию (уровнем ниже).
Мне кажется, написать продвинутый прекомпилятор – достаточно простая задача, по крайней мере я бы написал этот qfor6 за пару дней, и правильные инлайновые функции ещё за пару месяцев.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
K>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
Ну вообще что-то похожее темплейтами реализуется — когда у тебя есть темлейт, который введённый qfor6 разворачивает.
Здравствуйте, Khimik, Вы писали:
K>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
есть например макропроцессор m4, попробуй его посмотреть
Здравствуйте, Khimik, Вы писали:
K>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер? K>Я имею в виду следующее (на примере Delphi). Предположим, я пишу библиотеку или exe-файл, которому на вход подаётся Delphi-юнит (паскалевский код), и моя программа его немного модифицирует. Среда разработки показывает в двух вкладках два кода: исходный юнит и юнит, модифицированный моим прекомпилятором. При запуске программы компилируется второй код, и естественно отладчик имеет дело с ним. K>Вот пример. Есть простой приём оптимизации циклов – размножение. Пусть у меня есть код:
K>
K>qfor6 i:= 0 to count-1 do
K> values[i] := values[i]* values[i];
K>
K>Мой прекомпилятор видит qfor6 и преобразует этот код таким образом:
Осталось сравнить оба варианта на реальном измерении скорости. ПОдозреваю, что вариант с обычным for работать быстрее, потому что будет оптимизирован встроенным оптимизатором.
K>Я имею в виду следующее (на примере Delphi). Предположим, я пишу библиотеку или exe-файл, которому на вход подаётся Delphi-юнит (паскалевский код), и моя программа его немного модифицирует.
Поздравляю! Ты переизобрел препроцессор.
В Паскале его конечно нет, но можно вполне использовать m4 — Я пробовал на JAVA и на документации к проекту (всякие README) остался доволен.
K>>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
S>Ну вообще что-то похожее темплейтами реализуется — когда у тебя есть темлейт, который введённый qfor6 разворачивает.
Templates? В Delphi XE они есть?
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
K>>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
LS>есть например макропроцессор m4, попробуй его посмотреть
Мне пока главное — к Delphi этот m4 подключить нельзя?
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
S>Осталось сравнить оба варианта на реальном измерении скорости. ПОдозреваю, что вариант с обычным for работать быстрее, потому что будет оптимизирован встроенным оптимизатором.
А в самом деле, как компилятор Delphi XE8 поддерживает многоядерность/многопроцессорность?
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
K>(По ходу вопрос – насколько можно ещё оптимизировать этот код, или это уже предел?)
Вообще-то для операциях на векторах есть SIMD инструкции.
Хороший компилятор в состоянии их сгенерировать из такого года.
Когда всё же не может, можно явно реализовать через интринсики, например, здесь, возможно, была бы _mm_mul_epi32.
Да, цикл разворачивать хороший компилятор тоже умеет, это вовсе не сложно.
Сложнее вопрос, стоит ли это делать в конкретном случае.
Поэтому такая оптимизация делается компилятором не очень часто, только для совсем небольших циклов.
Ну то есть, я бы вернул всё как было с циклом, если бы был уверен в Delphi компиляторе.
Здравствуйте, Khimik, Вы писали:
K>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер?
В некоторых современных языках есть, называется "макросы" (macro). Представляет из себя функцию, которая получает собранный компилятором AST и преобразует его.
Качественных макросов на уровне исходного кода не встречал.
Здравствуйте, Khimik, Вы писали:
K>Интересно, не пробует ли кто-то из авторов ЯП делать кастомные прекомпиляторы, которые может написать программист-юзер? K>Я имею в виду следующее (на примере Delphi). Предположим, я пишу библиотеку или exe-файл, которому на вход подаётся Delphi-юнит (паскалевский код), и моя программа его немного модифицирует. Среда разработки показывает в двух вкладках два кода: исходный юнит и юнит, модифицированный моим прекомпилятором. При запуске программы компилируется второй код, и естественно отладчик имеет дело с ним. K>Вот пример. Есть простой приём оптимизации циклов – размножение. Пусть у меня есть код:
K>
K>qfor6 i:= 0 to count-1 do
K> values[i] := values[i]* values[i];
K>
Если брать конкретно этот пример, то unroll'инг без проблем реализуется без макросов через функции высших порядков:
transform(values, begin(values), [](const auto &x)
{
return x*x;
});
Причём под капотом transform может делать не только unroll, но и например разбивку на множество потоков.
На C++, такая абстракция в виде ФВП будет бесплатной (при включённой оптимизации) — то есть результат будет идентичен ручной раскрутке.
Если же в общем, то как упомянули выше — в некоторых языках есть встроенные синтаксические макросы.
Но ты, как я понимаю, спрашиваешь про внешние к коду преобразования. Тут могут быть разные варианты, в зависимости от степени интеграции с языком.
При слабой интеграции — будет примитивное манипулирование строчками кода — обычный внешний препроцессор.
Примером более сильной интеграции является Clang ASTMatchers + Rewriter: мы описываем паттерн синтаксического дерева кода который нас интересует, получаем callback при нахождении соответствующего паттерна, и переделываем синтаксическое дерево как нам необходимо: https://www.youtube.com/watch?v=mVbDzTM21BQ https://github.com/eliben/llvm-clang-samples/blob/master/src_clang/matchers_rewriter.cpp
K>(По ходу вопрос – насколько можно ещё оптимизировать этот код, или это уже предел?)
Зависит от того что у тебя за типы значений, и в целом что за программа.
Если это линейная алгебра, то имеет смысл использовать более высокие абстрации типа векторов и матриц, и оперировать непосредственно ими, а не педалить каждый цикл вручную.
Нормальные библиотеки (например Eigen) умеют оптимизировать линейную алгебру на уровне деревьев выражений. Например выражение вида:
EP>Причём под капотом transform может делать не только unroll, но и например разбивку на множество потоков. EP>На C++, такая абстракция в виде ФВП будет бесплатной (при включённой оптимизации) — то есть результат будет идентичен ручной раскрутке.
Вроде в Delphi XE8 ничего такого нет? И обычный for этого не делает?
Я писал, что обнаружил что инлайновые функции в Delphi XE8 неудобные. Мне кажется тут дело в том, что при развёртывании кода (размножение цикла, инлайновые функции и т.п.) становится очень трудно сделать взаимодействие отладчика с работающим кодом.
Поэтому правильный ЯП, по-моему, должен показывать два кода: исходный и прекомпилированный, где всё уже развёрнуто. Естественно отладчик будет иметь дело со вторым.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
EP>>Причём под капотом transform может делать не только unroll, но и например разбивку на множество потоков. EP>>На C++, такая абстракция в виде ФВП будет бесплатной (при включённой оптимизации) — то есть результат будет идентичен ручной раскрутке. K>Вроде в Delphi XE8 ничего такого нет? И обычный for этого не делает?
Вопрос как я понял был в общем, а Delphi здесь был лишь примером.
(про Delphi в целом — вообще не понимаю зачем его сейчас (2018 год) использовать, разве что legacy какое древнее. Уж тем более не понимаю зачем на нём пытаться оптимизировать раскрутку циклов. (сам пользовался им и Pascal'ем лет 15 назад))
K>Я писал, что обнаружил что инлайновые функции в Delphi XE8 неудобные. Мне кажется тут дело в том, что при развёртывании кода (размножение цикла, инлайновые функции и т.п.) становится очень трудно сделать взаимодействие отладчика с работающим кодом. K>Поэтому правильный ЯП, по-моему, должен показывать два кода: исходный и прекомпилированный, где всё уже развёрнуто. Естественно отладчик будет иметь дело со вторым.
Если речь про пошаговую отладку — то обычно достаточно попросить отладчик пропускать определение исходники при step-into (например предикат для пути).
Но методологически этот самый step-into должен быть не первым средством отладки, а одним из последних рассматриваемых.
EP>Вопрос как я понял был в общем, а Delphi здесь был лишь примером. EP>(про Delphi в целом — вообще не понимаю зачем его сейчас (2018 год) использовать, разве что legacy какое древнее. Уж тем более не понимаю зачем на нём пытаться оптимизировать раскрутку циклов. (сам пользовался им и Pascal'ем лет 15 назад))
У меня сильное подозрение, что Delphi — очень хороший язык, хорошо подходящий для высокоскоростных приложений, который пал жертвой неправильной моды (что-то вроде фишеровского убегания в программировании).
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
EP>>Вопрос как я понял был в общем, а Delphi здесь был лишь примером. EP>>(про Delphi в целом — вообще не понимаю зачем его сейчас (2018 год) использовать, разве что legacy какое древнее. Уж тем более не понимаю зачем на нём пытаться оптимизировать раскрутку циклов. (сам пользовался им и Pascal'ем лет 15 назад)) K>У меня сильное подозрение, что Delphi — очень хороший язык, хорошо подходящий для высокоскоростных приложений, который пал жертвой неправильной моды (что-то вроде фишеровского убегания в программировании).
Объективные факты в следующем:
— в Delphi как языке нет фич необходимых для высокоскоростных приложений, которые уже есть в других языках
— библиотеки структур данных и алгоритмов крайне скудные, опять же по сравнению с другими языками
— набор оптимизаций реализованных в компиляторе — также скудный
— пользовательская база мизерная, и IMO со временем только будет уменьшаться — нет ни потребности ни мотивации в улучшении предыдущих трёх пунктов
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Объективные факты в следующем: EP>- в Delphi как языке нет фич необходимых для высокоскоростных приложений, которые уже есть в других языках EP>- библиотеки структур данных и алгоритмов крайне скудные, опять же по сравнению с другими языками EP>- набор оптимизаций реализованных в компиляторе — также скудный EP>- пользовательская база мизерная, и IMO со временем только будет уменьшаться — нет ни потребности ни мотивации в улучшении предыдущих трёх пунктов EP>
EP>А твои подозрения на чём основываются?
Я сейчас переписываю на Delphi старый алгоритм, точнее оптимизирую его. Получилось быстрее раз в десять (цифра условная).
И вот я думаю, что, с одной стороны, C++ даёт код, скажем, в два раза более быстрый, чем Delphi; но гораздо больше значение имеет оптимизация самого алгоритма, например скорость разных алгоритмов сортировки для больших массивов может отличаться в миллион раз. А поскольку оптимизированный алгоритм обычно более сложный и багоносящий, то очень важна удобная среда разработки, позволяющая отлавливать эти баги и вообще писать сложный структурированный код.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
K>Я сейчас переписываю на Delphi старый алгоритм, точнее оптимизирую его. Получилось быстрее раз в десять (цифра условная). K>И вот я думаю, что, с одной стороны, C++ даёт код, скажем, в два раза более быстрый, чем Delphi;
Значение имеет итоговая скорость программы. Если программа в 2x-50x медленнее за счёт того что ты взял Delphi, то это плохая программа с точки зрения "высокоскоростного кода" который мы обсуждаем.
K>но гораздо больше значение имеет оптимизация самого алгоритма,
Которая также без проблем делается и на C++
K>например скорость разных алгоритмов сортировки для больших массивов может отличаться в миллион раз. А поскольку оптимизированный алгоритм обычно более сложный и багоносящий, то очень важна удобная среда разработки, позволяющая отлавливать эти баги и вообще писать сложный структурированный код.
Баги в алгоритмах лучше всего отлавливаются чёткими компонентами и тестами, а не средой разработки. Готовых алгоритмических библиотек для C++ больше, они лучшего качества.
Но даже если допустить что среда решает — то вот прямо в той же среде где крутится Delphi, доступен и C++ (раньше назывался C++ Builder, или его уже выпилили?), не говоря уже о том что есть C++ IDE и от других производителей.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Баги в алгоритмах лучше всего отлавливаются чёткими компонентами и тестами, а не средой разработки. Готовых алгоритмических библиотек для C++ больше, они лучшего качества.
Тогда возникает вопрос: какие бывают компоненты и тесты для отладки? Как их можно классифицировать? Мне проще самому такое написать (если я правильно понял что вы имеете в виду компоненты, написанные на самом ЯП для встраивания в код).
Если тема большая, я отдельно создам её в ФП.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
EP>Значение имеет итоговая скорость программы. Если программа в 2x-50x медленнее за счёт того что ты взял Delphi, то это плохая программа с точки зрения "высокоскоростного кода" который мы обсуждаем.
Итоговая скорость определяется скоростью некоторых ключевых (лимитирующих) участков, только их и надо оптимизировать (возможно, переводить на ассемблер).
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Здравствуйте, Khimik, Вы писали:
EP>>Баги в алгоритмах лучше всего отлавливаются чёткими компонентами и тестами, а не средой разработки. Готовых алгоритмических библиотек для C++ больше, они лучшего качества. K>Тогда возникает вопрос: какие бывают компоненты и тесты для отладки? K>Как их можно классифицировать? Мне проще самому такое написать (если я правильно понял что вы имеете в виду компоненты, написанные на самом ЯП для встраивания в код).
Под компонентами я имею в виду разбиение задач на по-возможности ортогональные части, которые легко протестировать по-отдельности.
Например при реализации quicksort, можно выделить под-алгоритм partition — который легко про-тестировать отдельно, который к тому же ещё и полезен сам по себе. Плюс компоненты могут быть готовыми — например в стандартной библиотеки C++ уже есть std::partition.
Под тестами понимаю обычные unit и интеграционные тесты — если ты покрыл все краевые случаи алгоритма partition тестами — то на этот компонент можно полагаться при разработки других компонентов, и не тратить каждый раз время на перепроверку кода partition.
K>Если тема большая, я отдельно создам её в ФП.