Как известно,при обработке обычных (не связанных с fpu) исключений ставится процедура - фильтр, что очень даже экономит время на проверке диапазонов входных данных для операций, например деления : mov eax,1 div ecx - нет необходимости проверять (и терять на это время) содержимого ecx. Случится исключение деления на 0 - тогда и будем разбираться внутри обработчика, не случилось - времени не теряется. А можно ли как-то организовать подобный ход событий при работе с числами с плавающей запятой? Или проверка состояния fpu на предмет деления на ноль, переполнения и т.д. после каждого действия и связанные с этим потери времени - это неизбежно?
NoName Я наверное несколько запутано выразился. Проверить флаги - это понятно, но не хотелось бы без нужды их постоянно проверять. Сделать проверку флагов только по факту случившегося исключения и тогда разбираться, что конкретно произошло. Т.е. есть ли механизм контроля, который следил бы за исключениями fpu, и который информировал бы меня только в случае, если исключение имело место быть, а если исключений не было, то он бы меня не дергал и я не отвлекался бы без необходимости на проверку.
В драйвере: перехватывай #MF, устанавливай NE в CR0, отмаскируй в Control Word исключения + почитай мануалы Intel'a, будут тебе исключения.
cresta Почитай IA-32 volume 1, chapter 8: Programming with x87 FPU. Там (почти) все расписано. По крайней мере "включить\отключить" генерацию нужных исключений очень легко: fclex+fldcw. > "очень даже экономит время на проверке диапазонов входных данных для операций, например деления: mov eax,1 div ecx" Так то оно так, но при этом следует учитывать что генерация и обработка исключения в ОС занимает очень много времени, т.е. это действительно - исключительная ситуация. Поэтому выбор между проверкой диапазона и исключением зависит от логики работы программы: если это исключительная ситуация (типа "капут - сливай вода"), то ес-но рулит SEH, а если это просто особая ситуация (хотя возможно и редкая) и для нее предусмотрена особая обработка, то логично делать обычную проверку. И не стоит преувеличивать "траты времени", по крайней мере для целочисленных проверок - ну что такое test ecx,ecx по сравнению c div ecx - капля в море )
leo Допустим такой случай: расчёт арктангенса. Предварительная проверка, которая направит код в одно из трех возможных ветвлений, занимает довольно таки немало Code (Text): fld qword ptr [asin] fabs fld1 fcompp fstsw ax fwait shr al,1 sahf ja @valid jc @error @equ_1: fld qword ptr [asin] fmul qword ptr [pidiv4] fstp qword ptr [angle] ...... ret @valid: ;Atn(x / Sqr(-x * x + 1)) ...... ret @error: ...... ret Как тут выгодней?
Забыл сказать важную вещь. Большинство математических выражений fpu выполняет правильно. Даже 1 делить на ноль будет бесконечноть и тому подобное, так что имеет ли смысл обрабатывать деление на ноль? Другой случай если появляются нечисла, которые плодят огромное количество багов, про них было в статье эдмонда хорошо написано.
cresta > "Как тут выгодней?" Пример конечно не очень удачный, т.к. FPATAN работает с лююбыми операндами и ошибок деления на 0 и переполнений не возникает. Единственная неопределенность для FPATAN, когда оба операнда = 0 (ес-но NaN'ы не в счет). С другой стороны, это пример того что вариант asin = 1 является не ошибкой, а просто особой ситуацией. В таких случаях нужно смотреть по тактам - сколько у нас скушает проверка по сравнению с общем временем вычисления. Все трансцендентные функции кушают сотню-другую тактов, fdiv и fsqrt около полусотни. Сравнения fcom и fcomi мы вроде как-то рассматривали в одной из тем - это что-то около десятка тактов (для P6 поменьше, для P4 около или чуть больше). Перехода боятся тоже не стоит если он предсказуем, поэтому для редкой особой ситуации должен быть прыжок "вниз", а основная ветка должна идти сразу после jcc. А в твоем примере, кстати не очень хорошо - первой идет редкая ситуация, а для общего случая приходится прыгать. Ну а обработка исключения в винде съест у тебя неcколько тысяч тактов (реальные цифры см. в теме IsDebuggerPresent). Вот и думай )
NoName понятие бесконечность существует, вроде логично, а что потом делать с этой бесконечностью, если она - выходной результат некой ф-ции, как с ней дальше работать и как на неё реагировать leo Значит не стоит?. Просто свести к минимуму затраты времени на неизбежные проверки на входе. ОК. Спасибо.
cresta Значит не стоит Если хочешь, вот еще парочка аргументов. Говоря о "потерях времени" мы часто забываем о внеочередном \ параллельном \ спекулятивном исполнении. Вот и я упомянул о ~10 тактов латентности сравнения (т.е. готовности jcc) и забыл уточнить, что в потоке команд это в итоге даст потери не более нескольких тактов. Если правильно расположить ветки условных переходов с точки зрения статического предсказания (т.е. основную ветку расположить без прыжка - сразу за jcc), то с учетом спекулятивного исполнения главная ветка начнет выполняться еще до того как будут проанализированы все условия, т.к. операции в главной ветке по идее не должны зависить от цепочки fld1(или fldz)+fcomp+fstsw+ALU+jcc (если конечно по глупости не создать ложной зависимости по флагам типа inc, clc, cld и т.п.). Поэтому максимум потерь здесь это 2-3 такта на запуск операций в исполнительные порты. Можешь протестировать на wintest - с проверкой условия и без проверки - разницы скорее всего не заметишь (даже на fdiv и fsqrt, не говоря о трансцендентых тормозах). Ну а в случае выполнения условия получаем непредсказанный переход с откатом спекулятивного исполнения и автосбросом возможного исключения - а непредсказанный переход это всего 10-20 тактов и уж никак не тысяч, как в случае эксепшена. А что мы будем иметь если обрабатывать особую ситуацию в SEH'е ? Установить обработчик - надо ? Надо. Отключить на выходе - надо ? Обязательно. В итоге как минимум те же несколько тактов скушаем. Да и сам обработчик менее прозрачный, если не сказать более сложный получается. В твоем примере ты одним сравнением обошел две возможные ошибки - отрицательный операнд fsqrt и деление на 0. А в обработчике придется анализировать флаги исключений и\или EIP чтобы разобраться в каком месте произошла ошибка, да еще учесть разное сосотяние стека FPU для этих случаев. По моему - морока, хотя если есть желание - можно поупражняться ) А вывод я бы сделал такой: 1) основные исключения (инвалидная операция, деление на 0 и переполнение) лучше не маскировать, иначе в непредвиденной ситуации хлопот не оберешься - ошибки могут плодиться одна за другой и не известно когда они проявятся и в итоге трудно будет найти причину, 2) а проверочки все-таки делать явным образом - потери не велики, зато проще и понятнее.
cresta А если я тебе скажу, что в простейшем цикле fdiv+dec+jnz добавление проверки условия не увеличивает, а уменьшает время обработки на P4, ты поверишь или нет ) Сам не поверил - видимо какие-то тонкие эффекты (без проверки идет какое-то проскальзывание одного такта из трех => среднее увеличение на 0.3 такта на цикл, а при добавлении проверки все исчезает). Вот и подсчитывай такты по мануалам )
Может это проскальзывание - это пенальти по поводу спешки, и пенальти больше чем дополнительная проверка как раз на величину 0,3 ?
Да в цикле такие вещи вообще бесполезно смотреть, т.к. задержка на сравнение проявляется только в первом проходе, а при последующих за счет спекулятивного исполнения следующее сравнение идет во время предыдущего fdiv и задержка "съедается". А без цикла получается все "правильно" с поправкой на дискретность rdtsc на P4, т.е. разница либо 0 либо 4, т.е. как и предполагалась меньше 4-х. Кстати и эту задержку можно "скрыть" если перед сравнением выполнить полезную независимую fpu-операцию. В в твоем примере это может быть x*x, тогда запуск в порты fcomp и fstsw пройдет параллельно с fmul. Ну да ладно, вроде все ясно - пора завязывать с чертовским красноречием )