Здравствуйте, Danchik, Вы писали:
D>Деревья как правило обходятся визиторами, так мелкософт придумал. Игорь же придумал другой способ работы с деревьями, ну в тыщу раз удобней. Я как ни смотрю на код EF, плакать хочется от обилия визиторов и их сложности.
Хотелось бы узнать, кто такой Игорь и что за способ работы с деревьями он придумал?
Здравствуйте, a_g_99, Вы писали:
__>вот тут наступает ваш звездный час. объясните мне как работает си щарп vm в одном предложении. Как же проходит jit оптимизация по индуззски? __>а потом мы померяемся c jvm jit и тем что делает C1 и opto. и тут возможно наступит некоторое прозрение.
Бжыынн-бжыннн-дрын-дрын-дрынннн! Эрон-дон-дон! Движок запускается, машина едет, мощь и скорость, нигга и тачка! They see me rollin, they hatin!
Здравствуйте, Sinclair, Вы писали:
_>>Необходимость создавать дерево, обходить его с помощью рефлексии и генерировать нужный нам код при каждом вызове. Как минимум это должно было бы происходить один раз при старте приложения, а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). S>Совсем всегда делать всё на старте приложения не выйдет — expression tree может замыкать локальные переменные и прочие штуки, которые меняются от вызова к вызову. S>Но да, expression tree можно было бы строить заранее — грубо говоря, сделать его не переменной, а полем класса, статическим либо экземплярным.
Нет, никаких подобных ограничений нет. Более того, это можно делать даже на стадии компиляции и ничего не изменится. Тебя же вот не удивляет, почему работают обычные замыкания или обычные операторы if? ))) Так почему тебя удивляет идея генерации простейшего кода с ветвлением и замыканиями? Т.е. метакод генерирует на стадии компиляции обычный код, занимающийся генерацией sql запроса на основание динамических данных. При этом стадия создания и анализа дерева выражений до рантайма уже не доходит. Это тривиально реализуется и используется в языках с нормальным МП и даже в некоторых языках с кривым МП выходит.
S>А вот построение кода из expression tree делать на стадии компиляции — идея плохая. Скажем, тот код, который я написал для linq2d, в такой системе невозможен. S>Потому, что в зависимости от того, какую версию AVX поддерживает процессор, порождается разный код. И внутри этого кода уже нет никаких ветвлений. Порождение кода в рантайме — хорошая, правильная идея. S>А накладные расходы на компиляцию устраняются при помощи кэширования. Надёжнее всего — явное кэширование: мы получаем из выражения некоторый transform — делегат, который удерживает ссылку на готовый код. S>Чуть менее надёжно неявное кэширование — это когда мы автоматически складываем все порождаемые функции в таблицу, индексированную исходным выражением. S>Уменьшается надёжность за счёт того, что любое кэширование имеет риск выбросить то, что нужно, или хранить то, чего не нужно.
С чего это невозможен? Если хочешь именно динамической генерации кода, то просто оставляй весь этот твой вышеописанный код на стадию исполнения. А вот генерацию дерева и его обход делать уже не нужно будет. Более того, можно будет и вызов этого твоего кода генерации по нормальному оформить (один раз, а не каждый раз (пусть и с кэшированием, что в прочем тоже не бесплатно)).
Хотя лично я всё же предпочёл бы статическую генерацию и конечного кода, потому что там точно можно не беспокоиться о времени компиляции и соответственно включить все мыслимые оптимизации. И, как это сделано во многих числодробильных библиотеках, просто предусмотреть несколько версий под разных архитектуры процессора, с выбором (просто переустановкой таблицы указателей на функции) при старте приложения.
Но в любом случае и в том варианте и в другом весь анализ дерева выражений без проблем можно производить на стадии компиляции.
S>Опять же, неявное кэширование проигрывает потому, что надо сравнивать expression trees. Если бы у нас expression tree порождались статически, то можно было бы сравнивать их по ссылке за O(1). S>А поскольку они порождаются динамически, их надо честно сравнивать. В реальности, порождение expression tree — крайне дешёвая операция, как и сравнение двух деревьев.
Вот вот.
Ну а насчёт дешевизны — это как всегда относительно. Как мы когда-то уже обсуждали, если у тебя запрос к СУБД обрабатывается десятки секунд, то даже его генерация с помощью скрипта на bash'е будет вполне себе эффективной. ))) Только вот не все запросы могут быть такими...
Здравствуйте, Ночной Смотрящий, Вы писали:
_>>Необходимость создавать дерево, обходить его с помощью рефлексии и генерировать нужный нам код при каждом вызове. Как минимум это должно было бы происходить один раз при старте приложения, НС>https://linq2db.github.io/api/LinqToDB.CompiledQuery.html
А что, оно уже научилось делать динамические (зависящие по структуре от рантайм параметра) запросы? )
_>>а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). НС>Ага, причем сразу под все поддерживаемые БД.
Если требуется динамическое переключение между ними, то да. А в чём проблема то? )
Здравствуйте, Слава, Вы писали:
_>>Необходимость создавать дерево, обходить его с помощью рефлексии и генерировать нужный нам код при каждом вызове. Как минимум это должно было бы происходить один раз при старте приложения, а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). С>Вы немало пишете на этом форуме, и я хорошо помню ваши споры про linq. Может быть вы тогда займётесь разработкой инструментария вокруг Nemerle, чтобы оное метапрограммирование наконец-то заработало и в дотнете?
Мне в данное время совсем не интересно развитие платформы .Net. А на интересных мне сейчас платформах данные инструменты уже активно развиваются (например https://github.com/launchbadge/sqlx).
P.S. А Nemerle там ещё не умер совсем? На фоне ммм карьерных изменений у его основных авторов мне казалось что там всё стало совсем печально. Правда это было несколько лет назад и с тех пор я не интересовался вопросом. И да, Nemerle мне всегда очень даже нравился. Жаль они выбрали немного не ту платформу (для её ориентации преимущества Nemerle были мало понятны).
Здравствуйте, Ночной Смотрящий, Вы писали:
_>>Ну раз ты так разбираешься в теме, то конечно же без проблем покажешь место в исходниках linq2db (вроде как лучший представитель из всего семейства linq2database решений), где используется visitor, не так ли? НС>Хочешь сказать что его там нет или что?
Т.е. ты сам даже не в курсе вопроса, но при этом радостно написал "Ты думаешь ему об этом раньше не говорили?". Что-то ты всё больше и больше скатываешься от технического специалиста до неграмотного тролля...
Здравствуйте, Sinclair, Вы писали:
_>>Да, я вот точно так же делаю, когда кто-то говорит, что Linq хорошо подходит для работы с коллекциями (перечисленное в цитате выше — это как раз примеры такой работы). S>Ну, то есть вы будете продолжать игнорировать аргументы. На всякий случай напомню, что наш предыдущий разговор, на который вы тут ссылаетесь, закончился вовсе не тем, что "я согласился, что линк плохо подходит для 2d-массивов", а тем, что вы сбежали через выход "но ведь то же самое можно было бы сделать, просто вызывая обычные методы".
Ну так а тогда у тебя именно так и было: была написана не плохая числодробильная функция, зачем-то "прилепленная" к linq. С тех пор что-то принципиально изменилось? Я тебе тогда ещё сказал, что подобный аргумент абсолютно равносилен написанию функции для решения диффуров, вызовом её по нажатию кнопки на WPF форме, и заявлению после этого, что WPF очень удобный инструмент для решения диффуров.
S>Как впоследствии оказалось, даже это не совсем так. К примеру, бинаризацию по Sauvola переписать на "обычные методы" будет крайне громоздко — конструкция let сильно сокращает "процедурный" код. S>А без неё сделать нельзя — expression trees не позволяют заводить временные переменные и делать присваивания. Это ограничение было бы дефектом, если бы оно не упрощало компилятор и не гарантировало отсутствие побочных эффектов, в отличие от настоящих присваиваний.
Эммм, мне кажется или ты это сейчас пытаешься недостаток linq выдать за такое себе своеобразное преимущество linq?
S>Про двоичный поиск и рекурсию, емнип, всё было сказано ещё в 2018 — если коллекция позволяет делать двоичный поиск (или вообще хеш-обращение за O(1)), то нет никаких проблем превратить то же самое linq-выражение в поиск по индексу. И такой проект есть, я давал на него ссылку. Он работает быстрее, чем прямой перебор, на коллекциях от сотни элементов. S>Преимущество же у linq перед рукопашным вызовом бинарного поиска очевидно — локализация изменений. S>Пишем идиоматический код; смотрим на производительность. Если нам кажется, что можно наиграть на бинарном поиске — заменяем List<string> на SortedList<string>, замеряем производительность. S>Может быть, мы проиграем — поддержание списка в отсортированном состоянии окажется дороже, чем экономия на поисках. S>Ок, заменим на какое-нибудь сбалансированное дерево — и снова посмотрим. У нас есть гарантия того, что код продолжит работать корректно. В вашем подходе придётся кропотливо ползать по коду и следить, чтобы случайно не получилось нарушить инварианты, на которые опирается binary search.
Уууу какая отборная демагогия и наглые передёргивания. Сравнивать для разных "сторон" разный уровень гарантий и разный уровень требований по производительности. Причём сравнивать естественно по "обратному" реализации критерию. Не уж, тут такое не пройдёт. Ты давай определись, какой конкретно случай мы рассматриваем (естественно любой приличный инструмент должен отлично отрабатывать оба случая, т.к. они оба встречаются в работе). Или мы говорим про случай с непринципиальной производительностью, но важной параноидальной защитой инвариантов или же у нас случай с принципиальным быстродействием. Для каждого из них код пишется по разному. Вот напиши классическую реализацию и на linq (ха ха) для каждого из них и потом сравни. А то сравнил ориентированную на производительность классическую версию с ориентированной на сохранение инвариантов любой ценой версию linq, причём сравнил по критерию сохранения инвариантов. Ты считаешь тут всех на форуме дебилами, что никто не заметит такого очевидного передёргивания?
Здравствуйте, mrTwister, Вы писали:
_>>Необходимость создавать дерево, обходить его с помощью рефлексии и генерировать нужный нам код при каждом вызове. Как минимум это должно было бы происходить один раз при старте приложения, а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). T>Ну вот уж нет, спасибо. Это конец динамическим Linq запросам и декомпозиции сложных запросов на несколько мелких.
С чего бы это? Вполне себе работают и динамические запросы и декомпозиция. При статическом (на стадии компиляции) разборе запроса и генерации кода, занятого формированием sql запроса (склейкой строк, что по сути мгновенно). Причём это не в теории, а давно работающие в других языках библиотеки.
Здравствуйте, alex_public, Вы писали:
_>Нет, никаких подобных ограничений нет. Более того, это можно делать даже на стадии компиляции и ничего не изменится. Тебя же вот не удивляет, почему работают обычные замыкания или обычные операторы if? )))
Если посмотреть внутрь обычных замыканий или операторов if, то внутри окажется вызов оператора new на некотором классе; а уже потом метод этого объекта передаётся туда, куда идёт замыкание.
Примерно то же происходит при порождении expression trees — в месте вызова происходит серия вызовов методов по конструированию дерева.
Единственная разница — в том, что замыкания могут быть и статическими, тогда никакого new в call site не будет. А expression trees динамические всегда.
В принципе, это можно было бы улучшить в компиляторе, но неясно, насколько велик будет выигрыш.
_>Так почему тебя удивляет идея генерации простейшего кода с ветвлением и замыканиями? Т.е. метакод генерирует на стадии компиляции обычный код, занимающийся генерацией sql запроса на основание динамических данных.
Именно так и устроен linq. _>При этом стадия создания и анализа дерева выражений до рантайма уже не доходит. Это тривиально реализуется и используется в языках с нормальным МП и даже в некоторых языках с кривым МП выходит.
Стадия создания и анализа дерева выражений для типичных случаев — это микросекунды, которые не имеет смысла экономить. S>>А вот построение кода из expression tree делать на стадии компиляции — идея плохая. Скажем, тот код, который я написал для linq2d, в такой системе невозможен. S>>Потому, что в зависимости от того, какую версию AVX поддерживает процессор, порождается разный код. И внутри этого кода уже нет никаких ветвлений. Порождение кода в рантайме — хорошая, правильная идея. S>>А накладные расходы на компиляцию устраняются при помощи кэширования. Надёжнее всего — явное кэширование: мы получаем из выражения некоторый transform — делегат, который удерживает ссылку на готовый код. S>>Чуть менее надёжно неявное кэширование — это когда мы автоматически складываем все порождаемые функции в таблицу, индексированную исходным выражением. S>>Уменьшается надёжность за счёт того, что любое кэширование имеет риск выбросить то, что нужно, или хранить то, чего не нужно. _>С чего это невозможен? Если хочешь именно динамической генерации кода, то просто оставляй весь этот твой вышеописанный код на стадию исполнения. А вот генерацию дерева и его обход делать уже не нужно будет.
Собственно, динамическая генерация кода — это и есть обход дерева. Что именно вы предлагаете перенести на стадию компиляции? _>Более того, можно будет и вызов этого твоего кода генерации по нормальному оформить (один раз, а не каждый раз (пусть и с кэшированием, что в прочем тоже не бесплатно)).
Вполне бесплатно. _>Хотя лично я всё же предпочёл бы статическую генерацию и конечного кода, потому что там точно можно не беспокоиться о времени компиляции и соответственно включить все мыслимые оптимизации. И, как это сделано во многих числодробильных библиотеках, просто предусмотреть несколько версий под разных архитектуры процессора, с выбором (просто переустановкой таблицы указателей на функции) при старте приложения.
В числодробильных библиотеках так сделано от отчаяния, а не потому, что у них был выбор между статикой и динамикой. Более того, рекордсмены по перемножению матриц не только используют динамический код, они перед выбором стратегии исполнения проводят замеры производительности. Потому что помимо флагов типа "есть avx2/нет avx2" есть ещё и соотношения таймингов разных инструкций, более-менее уникальные для каждой модели процессора.
Даже если отвлечься от таймингов, одна только интеловская линейка — это пять поколений SIMD, не включая avx512. А avx512 — это не одно поколение, а россыпь взаимно ортогональных флагов.
А ещё есть ARM.
Вы всеръёз предлагаете порождать под сотню вариантов кода статически? Зачем? _>Но в любом случае и в том варианте и в другом весь анализ дерева выражений без проблем можно производить на стадии компиляции.
Это если у нас дерево выражений — статическое. Вся сила linq — в том, что в sql уезжает не одно запечённое дерево выражений, а разные его варианты. _>Вот вот. _>Ну а насчёт дешевизны — это как всегда относительно. Как мы когда-то уже обсуждали, если у тебя запрос к СУБД обрабатывается десятки секунд, то даже его генерация с помощью скрипта на bash'е будет вполне себе эффективной. ))) Только вот не все запросы могут быть такими...
Конечно. Если мы исполняем запрос ровно один раз, то большого смысла экономить на времени его подготовки нет.
А вот если мы исполняем один и тот же запрос множество раз, то кэширование выезжает на первый план. То есть прокрутили expression tree через компилятор — и всё, с этого момента исполнение столь же эффективно, как и выбор одного из вариантов статического кода по указателю.
Вы устанете искать сценарий, в котором статическое порождение кода выиграет что-то заметное у динамики. А вот сценариев проигрыша — масса.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, alex_public, Вы писали:
_>Ну так а тогда у тебя именно так и было: была написана не плохая числодробильная функция, зачем-то "прилепленная" к linq. С тех пор что-то принципиально изменилось? Я тебе тогда ещё сказал, что подобный аргумент абсолютно равносилен написанию функции для решения диффуров, вызовом её по нажатию кнопки на WPF форме, и заявлению после этого, что WPF очень удобный инструмент для решения диффуров.
Нет, так не работает. Вы можете попробовать переписать решение для sauvola (ссылку тут уже дважды привели) на цепочку методов и убедиться, что ничего не получится.
Точнее, получится — но код будет громоздким и плохочитаемым. А весь смысл — как раз в том, чтобы дать пользователю писать короткий и понятный код, не теряя ни в безопасности, ни в производительности.
Я, пока отлаживал linq2d, нашёл у себя штук пять ошибок в изначальном процедурном коде sauvola. Причём эти ошибки без linq я мог бы вовсе и не заметить — картинку-то код давал похожую на нужную. Где-то плюс с минусом перепутаны, где-то x с y. Слишком много boilerplate.
В простых случаях типа a[i,j]+b[i,j] разница невелика, но как только начинается более-менее интересная математика — типа того же Саволы, — так сразу видно, какой вариант лучше.
S>>Как впоследствии оказалось, даже это не совсем так. К примеру, бинаризацию по Sauvola переписать на "обычные методы" будет крайне громоздко — конструкция let сильно сокращает "процедурный" код. S>>А без неё сделать нельзя — expression trees не позволяют заводить временные переменные и делать присваивания. Это ограничение было бы дефектом, если бы оно не упрощало компилятор и не гарантировало отсутствие побочных эффектов, в отличие от настоящих присваиваний. _>Эммм, мне кажется или ты это сейчас пытаешься недостаток linq выдать за такое себе своеобразное преимущество linq?
Этот "недостаток" быстро перестаёт казаться недостатком, как только сам садишься писать компилятор для деревьев выражений.
_>Уууу какая отборная демагогия и наглые передёргивания. Сравнивать для разных "сторон" разный уровень гарантий и разный уровень требований по производительности. Причём сравнивать естественно по "обратному" реализации критерию. Не уж, тут такое не пройдёт. Ты давай определись, какой конкретно случай мы рассматриваем (естественно любой приличный инструмент должен отлично отрабатывать оба случая, т.к. они оба встречаются в работе). Или мы говорим про случай с непринципиальной производительностью, но важной параноидальной защитой инвариантов или же у нас случай с принципиальным быстродействием. Для каждого из них код пишется по разному.
Это у вас в C++ надо выбирать между производительностью и инвариантами. Лично я хочу больше. _>Вот напиши классическую реализацию и на linq (ха ха) для каждого из них и потом сравни. А то сравнил ориентированную на производительность классическую версию с ориентированной на сохранение инвариантов любой ценой версию linq, причём сравнил по критерию сохранения инвариантов. Ты считаешь тут всех на форуме дебилами, что никто не заметит такого очевидного передёргивания?
Вот есть i4o — там опять всё быстро и гарантированно корректно. Не нужно выбирать между производительностью и безопасностью.
Для 2d массивов я написал Linq-версию. Она одновременно быстрая и безопасная. Т.е. попытка выхода за границы массива приводит к OutOfBoundsException, но при этом сами проверки устраняются в рантайм. Ничего близкого "классический" случай предложить не может.
Ну, и кто тут передёргивает?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, alex_public, Вы писали:
_>>>Ну раз ты так разбираешься в теме, то конечно же без проблем покажешь место в исходниках linq2db (вроде как лучший представитель из всего семейства linq2database решений), где используется visitor, не так ли? НС>>Хочешь сказать что его там нет или что? _>Т.е. ты сам даже не в курсе вопроса,
Я в курсе. И Danchik рядом тебе ответил на твой вопрос. Что дальше то?
Здравствуйте, alex_public, Вы писали:
_>>>Необходимость создавать дерево, обходить его с помощью рефлексии и генерировать нужный нам код при каждом вызове. Как минимум это должно было бы происходить один раз при старте приложения, НС>>https://linq2db.github.io/api/LinqToDB.CompiledQuery.html _>А что, оно уже научилось делать динамические (зависящие по структуре от рантайм параметра) запросы? )
Не понял вопроса. На момент компиляции да — вся динамика доступно в полном объеме.
_>>>а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). НС>>Ага, причем сразу под все поддерживаемые БД. _>Если требуется динамическое переключение между ними, то да. А в чём проблема то? )
Да никакой. Давай статически скомпилируем все возможные комбинации.
Здравствуйте, Sinclair, Вы писали:
_>>Так почему тебя удивляет идея генерации простейшего кода с ветвлением и замыканиями? Т.е. метакод генерирует на стадии компиляции обычный код, занимающийся генерацией sql запроса на основание динамических данных. S>Именно так и устроен linq.
Нет, он устроен не так. Если очень сильно упростить и линеаризовать, то такой простейший код:
q=select(...).from(...)
if(p) q=q.where()
В случае linq преобразуется во что-то типа:
covert(et){
q=''
for(e in et){
switch(e){
case 'select_op': q+=' select'
case 'from_op': q+=' from'
case 'where_op': q+=' where'
...
}
}
return q
}
et=['select_op', 'from_op']
if(p) et+=['where_op']
q=convert(et)
А после отработки (на этапе компиляции) метакода, мы получим такой код:
q='select'+' from'
if(p) q+=' where'
_>>При этом стадия создания и анализа дерева выражений до рантайма уже не доходит. Это тривиально реализуется и используется в языках с нормальным МП и даже в некоторых языках с кривым МП выходит. S>Стадия создания и анализа дерева выражений для типичных случаев — это микросекунды, которые не имеет смысла экономить.
Ой, вот не надо этих сказок. На этом форуме уже выкладывались подробные измерения этого вопроса. Причём сделанные, не такими как я, а профессионалами в .net. И там были вполне себе дикие цифры. Например для самого популярного linq фреймворка (EF) там встречались цифры порядка +500% от времени голого (ado.net) sql запроса, а для самого лучшего (Linq2db) встречалось +60% от времени запроса.
Хотя конечно если для твоей задачи подобные цифры не принципиальны, то действительно "не имеет смысла экономить". Но я думаю далеко не у всех такие задачи...
_>>С чего это невозможен? Если хочешь именно динамической генерации кода, то просто оставляй весь этот твой вышеописанный код на стадию исполнения. А вот генерацию дерева и его обход делать уже не нужно будет. S> Собственно, динамическая генерация кода — это и есть обход дерева. Что именно вы предлагаете перенести на стадию компиляции?
См. пример выше.
_>>Более того, можно будет и вызов этого твоего кода генерации по нормальному оформить (один раз, а не каждый раз (пусть и с кэшированием, что в прочем тоже не бесплатно)). S>Вполне бесплатно.
Да? ) И какой же индекс ты предлагаешь использовать для этого своего кэша? )
_>>Хотя лично я всё же предпочёл бы статическую генерацию и конечного кода, потому что там точно можно не беспокоиться о времени компиляции и соответственно включить все мыслимые оптимизации. И, как это сделано во многих числодробильных библиотеках, просто предусмотреть несколько версий под разных архитектуры процессора, с выбором (просто переустановкой таблицы указателей на функции) при старте приложения. S>В числодробильных библиотеках так сделано от отчаяния, а не потому, что у них был выбор между статикой и динамикой. Более того, рекордсмены по перемножению матриц не только используют динамический код, они перед выбором стратегии исполнения проводят замеры производительности. Потому что помимо флагов типа "есть avx2/нет avx2" есть ещё и соотношения таймингов разных инструкций, более-менее уникальные для каждой модели процессора. S>Даже если отвлечься от таймингов, одна только интеловская линейка — это пять поколений SIMD, не включая avx512. А avx512 — это не одно поколение, а россыпь взаимно ортогональных флагов. S>А ещё есть ARM. S>Вы всеръёз предлагаете порождать под сотню вариантов кода статически? Зачем?
В реальных библиотеках используются 2-3 варианта (обычно avx2, sse4, sse2), которые более менее принципиальны. Ну и для arm у нас просто Neon, но для него всё равно отдельная компиляция.
А если у нас задача, где реально надо выжать всё возможное, то всегда используется компиляция (статическая естественно) под конкретную машину (чаще всего прямо на ней же самой).
_>>Но в любом случае и в том варианте и в другом весь анализ дерева выражений без проблем можно производить на стадии компиляции. S>Это если у нас дерево выражений — статическое. Вся сила linq — в том, что в sql уезжает не одно запечённое дерево выражений, а разные его варианты.
_>>Ну так а тогда у тебя именно так и было: была написана не плохая числодробильная функция, зачем-то "прилепленная" к linq. С тех пор что-то принципиально изменилось? Я тебе тогда ещё сказал, что подобный аргумент абсолютно равносилен написанию функции для решения диффуров, вызовом её по нажатию кнопки на WPF форме, и заявлению после этого, что WPF очень удобный инструмент для решения диффуров. S>Нет, так не работает. Вы можете попробовать переписать решение для sauvola (ссылку тут уже дважды привели) на цепочку методов и убедиться, что ничего не получится. S>Точнее, получится — но код будет громоздким и плохочитаемым. А весь смысл — как раз в том, чтобы дать пользователю писать короткий и понятный код, не теряя ни в безопасности, ни в производительности.
Для таких простых алгоритмов самый обычный компилятор должен с гарантией делать автовекторизацию... Но это я так, к слову.
И для таких целей в нормальных языках имеется перегрузка операторов. Которая позволяет именно что читабельным образом задавать математику любого уровня сложности. Например если говорить про этот алгоритм, то на каком-нибудь numpy (конечно же скомпилированном с mkl) это и запишется проще и работать будет скорее всего быстрее. )))
_>>Уууу какая отборная демагогия и наглые передёргивания. Сравнивать для разных "сторон" разный уровень гарантий и разный уровень требований по производительности. Причём сравнивать естественно по "обратному" реализации критерию. Не уж, тут такое не пройдёт. Ты давай определись, какой конкретно случай мы рассматриваем (естественно любой приличный инструмент должен отлично отрабатывать оба случая, т.к. они оба встречаются в работе). Или мы говорим про случай с непринципиальной производительностью, но важной параноидальной защитой инвариантов или же у нас случай с принципиальным быстродействием. Для каждого из них код пишется по разному. S>Это у вас в C++ надо выбирать между производительностью и инвариантами. Лично я хочу больше.
Причём тут C++ или какой-то другой язык? В целом и на C# можно было бы всё сделать нормально. Конечно медленнее чем на C++ в силу слабости clr, но всё равно архитектурно правильно. Но не сделали. И кстати я что-то не припомню, чтобы я упоминал C++ тут...
Да, а по поводу выбора между производительностью и инвариантами — это у тебя будет в любом языке. Т.е. или ты доверяешь входным данным (позволяешь делать двоичный поиск по произвольным массивам, факт сортировки которых гарантируется "административно") или же не доверяешь (постоянно пересортировываешь их на всякий случай). Третьего варианта нет. Ну и естественно любой нормальный язык программирования должен позволять реализовать удобным образом оба сценария.
_>>Вот напиши классическую реализацию и на linq (ха ха) для каждого из них и потом сравни. А то сравнил ориентированную на производительность классическую версию с ориентированной на сохранение инвариантов любой ценой версию linq, причём сравнил по критерию сохранения инвариантов. Ты считаешь тут всех на форуме дебилами, что никто не заметит такого очевидного передёргивания? S>Вот есть i4o — там опять всё быстро и гарантированно корректно. Не нужно выбирать между производительностью и безопасностью. S>Для 2d массивов я написал Linq-версию. Она одновременно быстрая и безопасная. Т.е. попытка выхода за границы массива приводит к OutOfBoundsException, но при этом сами проверки устраняются в рантайм. Ничего близкого "классический" случай предложить не может. S>Ну, и кто тут передёргивает?
Эмм, какой ещё linq2d или i4o, если мы говорили про двоичный поиск? )))
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Sinclair, Вы писали:
_>>>Так почему тебя удивляет идея генерации простейшего кода с ветвлением и замыканиями? Т.е. метакод генерирует на стадии компиляции обычный код, занимающийся генерацией sql запроса на основание динамических данных. S>>Именно так и устроен linq.
_>Нет, он устроен не так. Если очень сильно упростить и линеаризовать, то такой простейший код: _>
_>q=select(...).from(...)
_>if(p) q=q.where()
_>
_>В случае linq преобразуется во что-то типа: _>
_>covert(et){
_> q=''
_> for(e in et){
_> switch(e){
_> case 'select_op': q+=' select'
_> case 'from_op': q+=' from'
_> case 'where_op': q+=' where'
_> ...
_> }
_> }
_> return q
_>}
_>et=['select_op', 'from_op']
_>if(p) et+=['where_op']
_>q=convert(et)
_>
Не совсем так, но что-то вроде. _>А после отработки (на этапе компиляции) метакода, мы получим такой код: _>
_>q='select'+' from'
_>if(p) q+=' where'
_>
В общем случае это невозможно. Либо генератор SQL будет совершенно убогим, либо результирующий код будет значительно сложнее. То есть прямо придётся идти по дереву и анализировать его в рантайме. Слишком много нюансов есть, которые не сводятся к if(p) q+=' where'. S>>Стадия создания и анализа дерева выражений для типичных случаев — это микросекунды, которые не имеет смысла экономить. _>Ой, вот не надо этих сказок. На этом форуме уже выкладывались подробные измерения этого вопроса. Причём сделанные, не такими как я, а профессионалами в .net. И там были вполне себе дикие цифры. Например для самого популярного linq фреймворка (EF) там встречались цифры порядка +500% от времени голого (ado.net) sql запроса, а для самого лучшего (Linq2db) встречалось +60% от времени запроса.
Дорогостоящей является стадия порождения целевого кода, будь это SQL или MSIL. Чем больше оптимизаций, тем дороже.
Само порождение дерева, которое вы мечтаете оптимизировать — микросекунды. Обход дерева, как таковой — тоже микросекунды.
_>См. пример выше.
Это воображаемый пример. В реальном примере вот этот вот лишний 'where' может привести к добавлению таблицы в список from и join-clause. Это означает что нельзя просто сделать q+='...', надо перепахать весь запрос.
То есть к моменту вызова if(p) у вас на руках будет не строка, а какое-то дерево (AST-представление SQL запроса).
Дальше нужно это дерево оптимизировать — после того, как вся пользовательская логика по ветвлению отработала. Невозможно провести глобальную оптимизацию "покусочно", в каждой из веток исполнения.
И только потом это дерево можно сериализовать в строку.
Построить тупую клеилку SQL, которая будет работать примерно со скоростью if(p) q+=' where' не очень трудно; но она встревает при встрече с реальными сценариями.
_>>>Более того, можно будет и вызов этого твоего кода генерации по нормальному оформить (один раз, а не каждый раз (пусть и с кэшированием, что в прочем тоже не бесплатно)). S>>Вполне бесплатно. _>Да? ) И какой же индекс ты предлагаешь использовать для этого своего кэша? )
Никакого индекса:
static private Function<byte[,], int[,]> _c4; // = from d in (new byte[0,0]).With(OutOfBoundsStrategy.NearestNeighbour) select (d[-1, 0] + d[0, -1] + d[1, 0] + d[0, 1]) / 4).Transform;public static int[,] C4(byte[,] data)
{
if(_c4==null)
{
var c4 = from d in data.With(OutOfBoundsStrategy.NearestNeighbour)
select (d[-1, 0] + d[0, -1] + d[1, 0] + d[0, 1]) / 4;
_c4 = c4.Transform;
}
return _c4(data);
}
Закомментированный кусок соответствует однократному построению фильтра при старте приложения, тогда ветвления в функции C4 можно избежать и получить вызов метода за примерно нулевую стоимость.
Чтобы сделать стоимость совсем нулевой, потребуется заменить тип C4 с делегата на интерфейс. Быстрее уже будет некуда — мы говорим о стоимости одного косвенного вызова.
_>В реальных библиотеках используются 2-3 варианта (обычно avx2, sse4, sse2), которые более менее принципиальны. Ну и для arm у нас просто Neon, но для него всё равно отдельная компиляция.
Это потому, что реальная библиотека пилится под какой-то конкретный случай. Например, работаем с double — для него нет разницы между sse2 и sse3.1.
Когда мы смотрим на код, в котором смешаны целочисленные и плавучие операции (как, например, в той же sauvola), всё становится гораздо кучерявее.
Ну, и avx-512 как бы тоже не за горами.
_>А если у нас задача, где реально надо выжать всё возможное, то всегда используется компиляция (статическая естественно) под конкретную машину (чаще всего прямо на ней же самой).
Ну вот CLR позволяет сделать компиляцию под конкретную машину на ней же самой. И это всё ещё будет быстрее, чем статическая компиляция С++ на этой машине.
_>Что за разные варианты? Ты вообще о чём?
О том самом if(p), и о разных диалектах SQL.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Ночной Смотрящий, Вы писали:
_>>Т.е. ты сам даже не в курсе вопроса, НС>Я в курсе. И Danchik рядом тебе ответил на твой вопрос. Что дальше то?
У меня был не вопрос, а предложение показать visitor в коде linq2db. Могу и тебе предложить тоже самое, ведь "Ты думаешь ему об этом раньше не говорили?" (c) Ночной Смотрящий.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>>>https://linq2db.github.io/api/LinqToDB.CompiledQuery.html _>>А что, оно уже научилось делать динамические (зависящие по структуре от рантайм параметра) запросы? ) НС>Не понял вопроса. На момент компиляции да — вся динамика доступно в полном объеме.
Давай возьмём простейший пример:
q=select(...).from(...)
if(p) q=q.where(...)
Я могу с помощью Linq добиться того, чтобы для такого кода проход по дереву выражений был ровно один раз, при старте приложения (лучше бы конечно вообще на этапе компиляции, ну да ладно)?
_>>>>а по нормальному вообще на стадии компиляции (с помощью метапрограммирования и статической интроспекции). НС>>>Ага, причем сразу под все поддерживаемые БД. _>>Если требуется динамическое переключение между ними, то да. А в чём проблема то? ) НС>Да никакой. Давай статически скомпилируем все возможные комбинации.
И в чём проблема? У тебя в приложение N запросов. При сборке под одну СУБД у тебя сгенерируется N кусков кода. А если захочешь поддерживать 3 СУБД, будет 3*N таких кусков. Непонятны твои затруднения. Или ты пожалел лишний килобайт на диске под исполняемый файл? )))