Угу. В 14+2*%1 -- не сможет.
В смысле - нафига козе баян... (мы, кстати говоря, обсуждали с Dilma эти вопросы на этапе разработки Fast-а)
А вот в 2*(%1+7) -- сможет. Ничем не хуже, между прочим...
Кстати, Fast тоже скобки не раскрывает.
Есть еще вопросы оптимизации...
Главный - не должно быть динамического выделения памяти в Run-Time.
Никаких аллокаций - и будет всем хорошо
Размер стека можно и при компиляции посчитать.
Да и длину MT-цепочки - тоже можно, наверное.
Этот топик читают: Гость
Ответов: 9906
Рейтинг: 351
|
|||
карма: 9 |
|
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Galkov писал(а): Размер стека можно и при компиляции посчитать. Оно только при первом вычислении выделяет столько, чтоб точно хватало, а потом использует уже имеющееся. Остается, конечно, небольшой оверхед (только в случае стека - не более 80 байт), если критично, можно и подсчитывать при компиляции, конечно. Galkov писал(а): Да и длину MT-цепочки - тоже можно, наверное.А вот это кстати классный вариант, достойное предложение. Galkov писал(а): А в 2*(%1+7) -- сможетНе, не сможет. Аргументы рассматриваются последовательно, и при первом встречном %n сразу будет "это не константа, значит пишем в программу". |
|||
карма: 10 |
|
Ответов: 9906
Рейтинг: 351
|
|||
А я говорю - сможет
|
|||
карма: 9 |
|
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Galkov писал(а): А я говорю - сможетВсе понятно, значит мне придется научить алгоритм смочь |
|||
карма: 10 |
|
Ответов: 8921
Рейтинг: 823
|
|||
Galkov, в алгебре (геометрии) много разных возможных сокращений, все-то их нет смысла учитывать или есть? Да и одно дело когда вычисление единственное, а другое в цикле 1000000. (Но вдруг в формуле что-то подобное (sin(%1))^2 + (cos(%1))^2, как не заменить на "+1" )
|
|||
карма: 19 |
|
Ответов: 9906
Рейтинг: 351
|
|||
Есть (точнее, было выработано при вышеупомянутом обсуждении с Dilma) очень практическое соображение: "А ну его нафиг!!!"
Не надо быть святее Папы Римского. Ну типа помочь пользователю - это одно. А думать за него - это другое. Скажем, корни фильтра Баттерворта равномерно расположены на круге. Коэффициенты фильтрации, соответственно, определяются косинусами каких-то очень простых константных выражений. Посчитать их в Compile-Time - это помощь. А думать за кого-то (раскрывать скобки, выделять общие подвыражения) -- бесперспективное занятие. Тут два философских варианта: Либо - все равно не поможет (вариант тупого юзера). Либо - не факт, что ты умнее Автора (вариант умного юзера). ------------ Дoбавленo в 08.21: 2Assasin, 1) Варнинг на 557-й можно убрать примерно так:
3) Внимание, 1334: 26, 30:, это вам не 26..30:. Тем более, что при "вычислении" - второй вариант остался ------------ Дoбавленo в 09.15: А вообще-то, функции min/max с одним аргументом -- смотрятся немного странно. Хотя и работают. Предлагаю сделать "вклейку":
|
|||
карма: 9 |
| ||
Голосовали: | Леонид |
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Galkov, подскажи пожалуйста, а каким образом была решена проблема преждевременного добавления операционных кодов? Например, есть выражение 2/(2+%1). Пусть %1 = 2 (компилятор об этом не знает). Очевидно, что результат = 0.5. Если не возвращать из каждой функции levelX структуру, в которой хранится либо промежуточный код, либо константа, (потому что это вариант, требующий значительной переделки алгоритма компиляции) то получается такая последовательность действий компилятора:
1. Получаем число 2 в функции оператора / 2. Входим в скобки. 3. Получаем число 2 в функции оператора + 4. Получаем переменную %1. 5. Функция оператора + видит, что выражение не вычислить на месте и последовательно пишет в код 2, %1 и операцию сложения. 6. Выходим из скобок. 7. Функция оператора / видит, что выражение в скобках не вычислилось на месте, и пишет свой аргумент в код, затем операцию деления. Итого, получим такую цепочку: 2, %1, +, 2, /. Выполнив ее, получаем (2+2)/2 = 2. Как решена такая проблема в FastMathParse? |
|||
карма: 10 |
|
Ответов: 9906
Рейтинг: 351
|
|||
В FastMathParse проблема сия решалась кошмарным образом.
Как позже оказалось. Не, когда планировалось - все казалось не очень сложным... Выполнение последнего действия откладывалось "на потом". Поэтому результатом разбора "была как бы формула" с одним оператором. И, соответственно, указывались типы аргументов. Аргументы могли быть: константа (она и указывалась), данные в памяти (указывался адрес, определяемый при компиляции), данные в стеке сопроцессора (которые являлись результатом вычисления, и именно для них имелся код). Какие тут возникали проблемы ... Самая первая, философская. Никаких внешних функций/подпрограмм вызвать нельзя, без полного сброса стека сопроцессора. Как бы, каждый программный модуль имеет права на ВЕСЬ стек сопроцессора (не такой уж и глубокий - 16, кажется). Поэтому сразу идут лесом всякие массивы, матрицы, функции... А внешние данные/аргументы читаются только один раз, перед вычислением. А компилятор таки смекает (в смысле - флажки расставляет), какие надо читать, а какие - нет. Получается, что они все, как бы сразу -- в кэше. Вторая проблема - конечный стек сопроцессора, а пользователя в длине формулы мы не ограничиваем. Возни на эту тему тоже хватало. Третья проблема - большая вариантность. Ну прикинь: надо сложить (потому что, например, мы сейчас находимся в Level1b) <константа минус память> и <произведение память на стек>. В этом случае, для второго аргумента считается, что "потом" уже наступило - в его код добавляется команда произведения вершины стека на память. Плюс к нему (к коду) добавляется команда реверсного отнимания от вершины стека <памяти> из первого аргумента. И в результате объявляем нашим результатом -- <константа плюс стек>. И т.д., и т.п.. Все эти варианты не страшные, просто их получилось до хрена. Столько, что пьяный ежик - хрен сломает И для меня, если честно, в случае необходимости внесения существенных изменений (например, арктангенсы какие нибудь) в FastMathParse -- нужно не менее недели, чтобы разобраться со своим же кодом... При этом, зная общую философию. Которую я излагал сейчас исключительно по памяти, не глядя в коды. Вот И, чего-то мне думается, что в случае MathParseEx - то же самое можно (и нужно) сделать гораздо проще. Сам тот факт, что две первые вышеуказанные проблемы начисто отсутствуют - достойная причина для самого MathParseEx. Как бы "переносимость" - немножко за уши притянуто. А вот отсутствие проблем масштабирования - аргумент объективный. Мысли о реализации вышеупомянутого безобразия - есть у меня... Расскажу о них. Через часик (перекур надо сделать) ------------ Дoбавленo в 10.17: Значит так... Мысль примерно такая: Assasin писал(а): (потому что это вариант, требующий значительной переделки алгоритма компиляции)Давай попробуем таки возвращать структуру, но так, чтобы это не требовало значительной переделки алгоритма компиляции. Но давало возможность пошагового добавления неких проверок, и возможных модификаций кода по результатам этих проверок. ((типа, если не хочешь, или еще не придумал как - не делай, никто не заставляет)) Например, объявим:
Вряд ли, это пока серьезные переделки. Грубо говоря, пока вообще никаких. Вот, а теперь -- чего мы себе думаем про смысл этих полей... А мы себе думаем, что код у нас ровно тот, что и сейчас, но если он получен с помощью константы, тогда первый адрес указывает на то место в "программе", где стоит CmdPush, а второй - на место, где этот "CmdPush" реализован. На последнюю команду, типа CmdAdd, в общем. Код боевой, ничего "на потом" не отложено, его можно запускать, не обращая внимание ни на что. А вот теперь, предположим, мы хотим прибавить еще константу. Если у нас первый адрес не нулевой, а по второму адресу стоит CmdAdd, то кто нам мешает прибавить эту константу, в Compile-Time, по адресу после CmdPush. Вроде бы никто. И это есть хорошо. В чем мысль состоит то: Занимаемся мы сложением в Level1b, скажем для формулы: (1 + %2 + (%1 - 2))/2 1) Получаем первым операндом {<адрес для CmdPush[1]>, nil, ... }. Это мы хотим, чтобы level6 именно так отреагировал на TokNumber. Почему бы и нет... 2) Получаем второй операнд (совместно с информацией о именно сложении) как {nil, <адрес для CmdRead>, ... } 3) Как и сейчас, притуляем в зад команду CmdAdd. Фиксируем в item адрес первой "константы", и адрес свежепритуленной команды. Кодинг пока, ровно тот же, что и сейчас. 4) Получаем третий операнд (совместно с информацией о именно сложении) как {<адрес для CmdPush[-2]>, <адрес для CmdAdd>, ... } 5) Если игнорировать содержимое tmpItem (это не наш аргумент, а локальная переменная, для возврата из других levelXXX), то ничего не остается, как притулить в зад CmdAdd. Что сегодня мы и делаем. А если подумать над содержимым tmpItem, то мы можем прибавить (-2) к ранее зафиксированной константе в item (по п.3 - и там получится -1), удалить из программы к чертовой матери CmdPush и CmdAdd для третьего опреранда. Ну и, естественно, притулить в зад свой собственный CmdAdd. .... Как бы получается, что мы создаем программу, как и раньше. НО, иногда, вырезаем из нее лишнее (этого метода еще нет в TProgram), и модифицируем константы (как бы это и есть вычисления в Compile-Time), туда уже зашитые. Подчеркну, это пока еще не опробованная мысль. Возможны некоторые "пеньки" по дороге. Но, попробовать стоит.... Мне кажется. ------------ Дoбавленo в 11.18: Да, я как бы по умолчанию предполагал, что сегодняшняя реализация TProgram - это уже за пределами добра и зла С точки зрения эффективности, конечно Надо делать примерно так:
Не думаю, что этот код настолько уж сложно прочитать Человеку Разумному. НО, использовать два функциональных вызова, плюс мухобойный Move, чтобы прочитать в Run-Time всего ОДЫН байт -- это же полный беспредел |
|||
карма: 9 |
| ||
Голосовали: | Assasin |
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Galkov, мне кажется, что выжимать максимум производительности из MathParseEx не стоит, все же когда нужен максимум, тогда уже есть готовый FastMathParse (если, конечно, не зайдет речь о замене FastMathParse на MathParseEx). Поэтому склоняюсь к текущему варианту, где программа и стек реализованы отдельными классами. Фактически, если я буду переписывать классы на переменные и ручную работу с ними, то я начну заниматься микрооптимизациями, которые на практике вообще за меня будет решать компилятор (заинлайниванием вызовов функций).
А вот расширить классы для оптимизации отдельных операций (например, не гонять переменную со стека и обратно) - это можно. Galkov писал(а): НО, использовать два функциональных вызова, плюс мухобойный Move, чтобы прочитать в Run-Time всего ОДЫН байт -- это же полный беспределЭто, пожалуй, верная мысль Read/WriteBinary оставить на случай строк, а остальные R/W переписать на прямую работу с указателями на конкретные значения. И тогда Move не потребуется. По поводу алгоритма - так до конца и не получилось понять, где мы резервируем, где режем лишнее, поэтому пришла следующая мысль: если операнду слева пришла константа, то он сразу резервирует в программе место под CmdPush, а потом, в конце, когда уже понятно, константное ли выражение, либо оставлять все, как есть (код записан в TProgram правильно), либо смещать позицию в программе обратно, тем самым как бы стирая CmdPush (и следующей записью перезаписывая его окончательно). |
|||
карма: 10 |
|
Ответов: 9906
Рейтинг: 351
|
|||
Философия - чуток по позже (ясный перец, что твоя - неправильная )
Но вот я не поленился, и "на скорую руку" ликвидировал вышеупомянутый беспредел (см. аттач) Там есть чего еще делать, и это вовсе не конечный вариант... А может и ошибки закрались... НО, почувствуйте разницу Хотя все почти то же самое... ------------ Дoбавленo в 14.41: Кстати говоря, перед тестированием на скорость -- снимите DebugMode ((мать его за ногу )) ------------ Дoбавленo в 19.18: У мине получилось собственное время ~5 раз быстрее (в смысле - меньше), в сравнении тем, чего було Не путать с тупым измерением полного времени Собственно, измерения проводились по этой схеме (пост на предыдущей странице в конце) |
|||
карма: 9 |
| ||
файлы: 1 | himathparse_qu.rar [8KB] [502] |
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Уменьшил собственное время в два раза, убрав использование Move для типов byte, integer, real. Теперь уберу гонение переменной из стека и обратно и продолжу тесты
|
|||
карма: 10 |
|
Ответов: 9906
Рейтинг: 351
|
|||
Про философию
Вот я тебе расскажу, чем Инженер отличается от IT-шника. Второму нужны доказательства, для того, чтобы делать лучше. А первому - доказательства, чтобы делать хуже. Первая культура давно существовала, когда еще и компьютеров не было. Существуют некие цеховые правила, паттерны разработки, наконец. Опробованные веками. Зато именно вторая считает себя носителем Истины, закручивая пальцы веером. По смыслу - IT должна бы быть инженерной культурой. И, может быть, когда нибудь ей и станет. Когда образованность ее носителей поднимется до уровня, позволяющего познакомиться с опытом предков. А вот тебе пример из нашего случая (применение философии на практике) Ну показал же я тебе
А оставаясь к концепции интерпретатора? Мне кажется, что нет. Но предложения принимаются. А может у кого есть иная оригинальная концепция? И поскольку я Инженер, а не IT-шник, то спрашиваю: Где доказательства, что надо делать ХУЖЕ (оборачивать это дело всякими там классами-дурасами) И вовсе НЕ СОБИРАЮСЬ доказывать, что надо делать лучше (эффективнее). А просто делаю. Где та Великая Причина, заставляющая писать на 200 строк кода больше, и терять в эффективности. Мало или много - даже считать не буду, потому что все равно терять. Где та Великая Причина, заставляющая меня (Читателя) ломать башку над использованием какого-нибудь FAlloced. Вопросы были риторические. Просто знаю, как обычно IT-народ отвечает: звучат религиозные лозунги, никакой связи не имеющие с поставленной целью. И вообще, парадигма работы Инженера предполагает обсуждение этих вопросов (размышление над этими вопросами) ДО кодинга, а не ПОСЛЕ. Сочиняется (придумывается) несколько вариантов развития. Несколько - это обязательно. Выбирается оптимальный по вышеозначенным критериям. И получается быстрее и эффективнее. Чего делают программисты, спрашивается. Они придумывают какой-то один вариант. Жутко гордятся, что они его придумали - работает же! Потом начинают профилировать. Где-то чего-то исправлять. Отлаживать. Ловить свежевнесенные баги в течении многих лет. И получается, что высококвалифицированный (!!!) программист больше 10000 строк кода за год написать не может. Обычно - не больше 3000. Нарушают один из основных принципов (из тех, которые опробованы веками) работы Инженера: Не планируй себе проблем, попробуй сначала справиться с теми, которые возникнут (а они возникнут) и без твоего планирования Конечно, это эмпирика. Но она всегда работала, и работает. Даже если ты ей не веришь Я понятно объясняю ------------ Дoбавленo в 12.25: Про конкретику: теперь буду "цепляться за фразы" Assasin писал(а): Galkov, мне кажется, что выжимать максимум производительности из MathParseEx не стоит, все же когда нужен максимум, тогда уже есть готовый FastMathParseНе надо выжимать. Выжимать - это и есть FastMathParse. И у него есть ограничения в функциональности. Надо просто не делать глупости. Это не трудно. Разве я этого тебе не показал Assasin писал(а): то я начну заниматься микрооптимизациями, которые на практике вообще за меня будет решать компилятор (заинлайниванием вызовов функций)Ага, ЩАЗ. Разогнался и подпрыгнул. Потом догнал, и еще раз соптимизировал. Вообще-то, я коды смотрел (в дизасме) Assasin писал(а): А вот расширить классы для оптимизации отдельных операций (например, не гонять переменную со стека и обратно) - это можноА еще можно не создавать себе проблему, и не придется ее решать. Assasin, еще один принцип Инженера: он работает не только для компьютера, но и для своего Коллеги. И второе, обычно более важно, чем просто "работает же" В этом коде:
А вот если ты "расширишь классы для оптимизации отдельных операций", то полазить по файлу - очень даже придется. Может и не один раз. А может там и ашипка закрадется. Причем, я сначала буду полдня думать (может это я дурак), перед коллективной разборкой. И всего этого могло не быть. Assasin писал(а): По поводу алгоритма - так до конца и не получилось понять, где мы резервируем, где режем лишнееДавай тогда говорить на языке знакомых букв и цифр... А вот твой же Dump для вышеупомянутой формулы (меня задрал CmdDbgLine, и я его заблокировал) (1 + %2 + (%1 - 2))/2
Вот, возвращаемся к Level1b (копировать не буду - смотри в код сам) Чего получается после первого вызова Level2 ??? Это первая строка в дампе (push 1). И мы как-то там, через еще недодуманную структуру Titem должны получить реальный адрес (скорее - индекс в массиве) этих 9-ти байт Дальше, следующий вызов Level2 добавляет нам вторую строку (read 1). И мы сами добавлям третью (add). Спрашивается, чего дает третий вызов Level2 ??? Он дает сразу три строки - 4-ю, 5-ю, и 6-ю. Вот, и мы себе думаем (точнее, пытаемся придумать), что при этом вернутся два адреса (или индекса) в переменной tmp (см. код). Первый показывает (в нашем случае) на 9 байт для 5-й строки (push 2), а второй - на 1 байт команды (в нашем случае) в 6-й строке (sub). Каков должен быть наш план в этом случае. Чтобы вернуть то, что мы потребовали (его же вернул такой же Level1b, но на более глубоком уровне рекурсии). Мы должны в аргумент item (см. код) занести адрес первой встреченной константы (в нашем случае это строка 1) и первого оператора, к ней примененного (в нашем случае это строка 3) ТОДЫ Отнимаем (ибо sub, а в формуле при этом стоит +) от константы, зафиксированной уже в item - двоечку (ибо push 2). Удаляем строки 5 и 6 (на самом деле, полученные в tmp после 3-его Level2). Должны получить: Так КУ, или не КУ ???
Повторюсь, пока это "мысли вслух" |
|||
карма: 9 |
|
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Хм, Galkov, не поверишь, но вот делая себе классы, я как раз считаю, что делаю лучше, и что избавляюсь от проблем, которые возникнут. И я не нахожу пока причин делать хуже (т.е. избавиться от классов и перейти к прямой работе с памятью). Я еще не инженер, конечно, и к концу обучения в универе мои взгляды на программирование могут поменяться ближе к твоей позиции, но на данный момент взгляды те, которые я выше озвучил.
У меня кстати часто наоборот проблема, что я не могу начать писать код, потому что слишком занят решением проблем на этапе проектирования. |
|||
карма: 10 |
|
Ответов: 9906
Рейтинг: 351
|
|||
Assasin писал(а): Galkov, не поверишь, но вот делая себе классы, я как раз считаю, что делаю лучше, и что избавляюсь от проблем, которые возникнут.Поверю. Вас так учат (ничего личного -- это я по своим коллегам сужу) Не избавляешься от проблем, а их закапываешь в реализации класса. НО делаешь более удобным их исправление. Ты эти проблемы запланировал. Затрудняешь чтение, создавая новые логические абстракции. Сказано: не создавай новые сущности без необходимости ((с)Окамм - вспомни сколь веков назад он жил) Вот я тебе только что показал 2 строки кода. Где эта необходимость для создания новой сущности А нет ее. Придумана. Кстати, ты ничего не сказал про FAlloced. Вот открой народу глаза, чего это за сепулька, и нафига она нужна. Прикинь, моего образования не хватило для понимания этой "абстракции" ------------ Дoбавленo в 13.17: Assasin писал(а): Я еще не инженерТы, главное, сильно не расстраивайся. С вероятностью 99%, ваши преподы - тоже об этом имеют весьма слабое представление. Беседовал тут я со своими знакомыми (преподы с ФИТ в НГУ). По сайтам апологетов "инженерного программирования" полазил.... Ужасть |
|||
карма: 9 |
|
Разработчик
Ответов: 4698
Рейтинг: 426
|
|||
Galkov писал(а): Вот я тебе только что показал 2 строки кода. Где эта необходимость для создания новой сущностиА нет ее. Придумана. Естественно. Для двух строчек кода она (сущность) и не нужна. Но когда таких вот мест, где используется две (а может и больше) строк кода, становится больше некоего критического числа, тогда сущность уже нужна. Ты можешь написать эти две строчки кода и сколь угодно долго не видеть подводных камней в них. А вот однажды смекалистый юзер такое применение найдет, что откроется бага в них. И потом уже по коду придется перепроверять все места, где подобные строчки использовались. А может оказаться, что из-за этого те же две строчки в другом месте решают задачу правильно всегда, а ты по инерции и их исправишь, тем самым еще один камень заложишь. Через месяцы этот камень обнаружится, и снова будешь править на старый вариант во всех местах... по кругу в общем По поводу идеального кода: да, можно писать идеальный код для решения очень конкретной задачи (суперкомпиляция - такой термин вроде бы есть, схожий по значению). Но когда ты вылизываешь код для решения конкретной задачи с конкретными диапазонами входных данных, можно просто забыть учесть маасенькую деталь, вроде того, что границы диапазона то на самом деле на одну сотую шире, и когда входные данные попадут в этот мизерный промежуток - бага вскроется. Физически невозможно уследить за всем. Поэтому стоит уважать и свое, и чужое время на поиск таких вот незаметных камешков. Возвращаясь к нашим баранам: если разница между классовым подходом и прямым в реализации интерпретации байткода окажется существенной (в 1.5-2 раза), то тогда я приму твою идею, т.к. тогда я увижу доказательства того, что эти меры реально делают лучше. За рекомендации в любом случае спасибо, как известно, годы не проходят просто так, рано или поздно всякий совет пригодится. Я чувствую, что еще не раз вспомню все, что ты мне тут писал. Поэтому еще раз спасибо ------------ Дoбавленo в 13.32: Galkov писал(а): Кстати, ты ничего не сказал про FAlloced.Вот открой народу глаза, чего это за сепулька, и нафига она нужна. Для того, чтобы хранить размер реально выделенной под поток байт памяти. Использоваться может меньше, а выделяться больше. Это чтобы меньше использовать динамическое выделение. |
|||
карма: 10 |
|