Русский
Русский
English
Статистика
Реклама

Fpu

Перевод числа в строку с помощью FPU

17.01.2021 06:18:51 | Автор: admin

Введение

Часто требуемое для вывода результатов расчетов преобразование числа с плавающей точкой из формата IEEE-754 в текстовую строку в научной нотации (т.е. с показателем степени E) не является совсем уж тривиальной задачей. В силу обстоятельств автору пришлось самостоятельно изобретать велосипед такого преобразования. Причем хотелось сделать это максимально эффективно, в полной мере используя аппаратные возможности обработки чисел.

Как ни странно, в литературе мне не попалось ни одного готового примера на эту тему. Поэтому описываемое ниже собственное решение призвано восполнить этот пробел в виде представленной ассемблерной процедуры, которая входит в системную библиотеку сопровождаемого транслятора в течение многих лет и, таким образом, надежно проверена на практике.

Постановка задачи

Формализуем задачу. Необходимо перевести восьмибайтовое число из формата IEEE-754 в текстовую строку, где указан знак числа, мантисса заданной длины и десятичный порядок с признаком E и своим знаком.

Так, если задано число 1234.567890, то строка с мантиссой, например, в 16 знаков должна выглядеть как 1.23456788999999E+003. Вместо плюса у мантиссы нужно писать пробел, а сама мантисса должна быть не меньше единицы. Кстати, данный пример иллюстрирует дискретность и приближенность представления чисел в формате IEEE-754: приведенное исходное число не может быть точно представлено как 1.23456789000000E+003, что, может быть, интуитивно ожидалось.

Использование команд FPU для преобразования

На первый взгляд, решение выглядит простым. В устройстве FPU (Floating Point Unit) процессора даже имеются команды, явно предназначенные и для перевода чисел из формата IEEE-754 в текст. Это, во-первых, команда EXTRACT разделения числа на мантиссу и порядок, а во-вторых, команда FBSTP выдающая мантиссу сразу в виде двоично-десятичного значения. Однако при ближайшем рассмотрении эти команды не дают нужного результата.

Например, если применить команду FBSTP к указанному выше числу, то получится 10 байт со значением 35 12 00 00 00 00 00 00 00 00, поскольку нецелочисленные значения сначала округляются согласно полю RC управляющего слова FPU. Это двухбитовое поле RC может принимать 4 значения, но среди них нет режима отключить округление. Поэтому часть мантиссы просто теряется, единственное чего можно добиться режимами округления это еще получить значение 34 12 при округлении к меньшему.

Да и порядок числа, так легко выделяемый командой EXTRACT, является все же степенью двойки, а не десятки, как требуется в данном случае для вывода.

Итак, при использовании возможностей FPU придется решать две задачи.

Во-первых, умножением (или делением) исходного числа на десять нужно перегнать всю мантиссу в целочисленную часть числа, тогда команда FBSTP выдаст, наконец, в двоично-десятичном виде все цифры мантиссы.

Во-вторых, нужно определить такой десятичный порядок (опять-таки умножением или делением на десять), при котором результат попадет в диапазон между 1 и 10. Это нужно для представления числа в виде мантиссы с одной цифрой перед точкой и найденным порядком. Увы, совместить эти две задачи в едином цикле умножения/деления невозможно.

Причем есть и подводный камень в виде значения числа, максимально приближенного к единице, но не равного единице. Циклом деления или умножения легко можно ошибиться в показателе степени и вместо требуемого в данном случае 9.999E-001 получить неправильное значение типа 9.999E+000. К сожалению, при всем богатстве команд FPU обойтись без циклов деления и умножения на десять не удается.

Алгоритм преобразования

При формировании мантиссы для числа типа double умножение или деление на 10 продолжаются до тех пор, пока двоичный порядок числа не достигнет (или не станет больше/меньше) 53, поскольку под мантиссу выделено именно 53 двоичных разряда.

При формировании десятичного порядка для числа типа double умножение или деление на 10 продолжаются до тех пор, пока двоичный порядок разницы числа и 1 не достигнет (или не станет больше/меньше) -53, что означает максимальное приближение к 1. При этом необходимо еще отслеживать частный случай ближайшего к 1 значения из одних девяток, поскольку цикл умножения или деления в этом случае дает одну лишнюю или одну недостающую степень.

Пример реализации преобразования

Далее приведена ассемблерная подпрограмма преобразования числа в текст именно так, как она записана в системной библиотеке. Хотя при этом появляются дополнительные детали, связанные с особенностями использования в конкретном трансляторе, я считаю правильнее ничего не выбрасывать и не превращать реализацию в короткий, но искусственный и не работающий во всех случаях пример.

Пара комментариев к тексту. Подпрограмма написана на языке ассемблера RASM, который имеет некоторые особенности.

В частности здесь используются временные метки в виде символа @. Алгоритм их реализации следующий: транслятор имеет внутренний счетчик меток. Когда метка @ встречается в команде перехода, к ней автоматически дописывается значение этого счетчика (т.е. реально создаются метки @0000, @0001 и т.д.). Когда встречается символ @ с двоеточием, к нему также автоматически приписывается значение счетчика и после этого счетчик увеличивается на 1.

Таким образом, получаются метки, на которые можно передавать управление только вперед и использовать многократно, не придумывая для каждой следующей метки новое имя. Поскольку в ассемблере метки часто используются лишь однократно для обхода группы команд, такой прием упрощает написание и анализ программы.

Кроме этого, для команд FPU в RASM не анализируется размер операнда, он имеется прямо в названиях команд в виде значений 16, 32, 64 или 80.

Но вернемся к задаче. Подпрограмме по адресу в EBX передается исходное восьмибайтовое число в формате IEEE-754 и требуемая длина текстовой строки-результата в AL.

В результате работы число записывается в стек в виде текста в научной нотации и в AL сохраняется длина этой строки. Другие регистры не сохраняются.

С целью разгрузить конвейеры процессора некоторые команды рассыпаны по тексту и не образуют непрерывных логических цепочек, что несколько затрудняет чтение текста. Но таких мест немного. Программа реентерабельна и может быть использована в параллельных вычислениях. При начале работы программы управляющее слово FPU CW=0360.

  1. Сначала в стеке выделяется место для ответа и заполняется нулевым шаблоном, т.е. значением 0.000 E+000.

  2. Затем проверяется знак числа и в зависимости от него формируется мантисса умножением или делением числа на десять.

  3. Командой FBSTP мантисса переписывается в память из FPU в двоично-десятичном виде и ее часть (заданной длины) переносится в ответ.

  4. После этого исходное число опять-таки умножением или делением на десять как можно ближе подгоняется к 1, что позволяет вычислить требуемый десятичный порядок, который и записывается на заранее подготовленное в ответе место.

      CSEGPUBLIC ?QFCMW:;---- ПОДГОТОВКА МЕСТА В СТЕКЕ ----      MOVZX     ECX,AL          ;ЗАДАННАЯ ДЛИНА СТРОКИ      POP       EAX             ;ДОСТАЛИ АДРЕС ВОЗВРАТА      SUB       ESP,ECX         ;ВДЕЛИЛИ МЕСТО В СТЕКЕ      MOV       EDI,ESP         ;ОНО ЖЕ НАЧАЛО СТРОКИ-РЕЗУЛЬТАТА      MOV       ESI,ESP         ;ЗАПОМНИЛИ НАЧАЛО      PUSH      EAX             ;АДРЕС ВОЗВРАТА ВЕРНУЛИ НА МЕСТО      PUSH      ECX             ;ЗАПОМНИЛИ ЗАДАННУЮ ДЛИНУ ОТВЕТА;---- СНАЧАЛА ЗАПОЛНЕНИЕ СТРОКИ-РЕЗУЛЬТАТА НУЛЕВМ ШАБЛОНОМ ----      MOV       EAX,'0.0 '      ;ЗНАК И ПЕРВОЕ ЧИСЛО С ТОЧКОЙ      STOSD      DEC       EDI             ;ОСТАВЛЯЕМ ПЕРВЕ ТРИ СИМВОЛА ШАБЛОНА      SUB       CL,8            ;ВЧЛИ СЛУЖЕБНЕ ЗНАКИ И ПОРЯДОК      MOV       AL,'0'      JBE       @               ;ОШИБКА, НЕТ МЕСТА ПОД МАНТИССУ      REP       STOSB           ;ЗАПОЛНИЛИ МАНТИССУ НУЛЯМИ@:    MOV       EAX,'00+E'      ;ПОКА ДВА НУЛЯ ПОРЯДКА      STOSD                     ;ЗАПИСАЛИ НУЛЕВОЙ ПОРЯДОК      LEA       EBP,[EDI]-2     ;ЗАПОМНИЛИ АДРЕС ПОРЯДКА      FLD64     [EBX]           ;ЗАГРУЗИЛИ ЗАДАННОЕ ЧИСЛО      MOV       B PTR [EDI],'0' ;ПОКА ТРЕТИЙ НУЛЬ ПОРЯДКА;---- ПРОВЕРКА ЗАДАННОГО ЧИСЛА НА ПЛЮС/МИНУС/НОЛЬ ----      FTST                      ;СРАВНИЛИ С НУЛЕМ      FNSTSW    AX      SAHF      JNZ       @               ;ЗАДАННОЕ ЧИСЛО НЕ НОЛЬ;---- СРАЗУ ВХОД С ЧИСТМ НУЛЕМ ----      POP       EAX             ;ВХОД С ЗАДАННОЙ ДЛИНОЙ И НУЛЕМ      FSTP      ST              ;ОЧИСТИЛИ FPU ОТ ИСХОДНОГО ЧИСЛА      RET;---- ОБРАБОТКА ЗНАКА ЗАДАННОГО ЧИСЛА ----@:    JNB       @               ;ЕСЛИ ЧИСЛО ПОЛОЖИТЕЛЬНО      MOV       B PTR [ESI],'-' ;ОТМЕТИЛИ ЗНАК ОТРИЦАТЕЛЬНОГО      FABS                      ;УБРАЛИ ЗНАК В САМОМ ЧИСЛЕ@:    MOV       EDX,OFFSET ДЕСЯТЬ ;ДЕСЯТИЧНАЯ СИСТЕМА;---- ПРОВЕРКА ВЕЛИЧИН ПОРЯДКА ИСХОДНОГО ЧИСЛА ----      FLD       ST              ;РАЗМНОЖИЛИ ПОЛОЖИТЕЛЬНОЕ ЧИСЛО      POP       ECX             ;ОПЯТЬ ДОСТАЛИ ДЛИНУ СТРОКИ      FXTRACT                   ;РАЗДЕЛИЛИ МАНТИССУ И ПОРЯДОК      PUSH      ECX             ;ОПЯТЬ СОХРАНИЛИ ДЛИНУ СТРОКИ      FSTP      ST              ;ВКИНУЛИ МАНТИССУ      SUB       ESP,11          ;ВДЕЛИЛИ МЕСТО ПОД МАНТИССУ КАК BCD      FIST32P   [ESP]           ;ЗАПИСАЛИ ПОРЯДОК      CMP       D PTR [ESP],53  ;ОН УЖЕ 53 РАЗРЯДА ?      JE        M0003           ;ТОГДА МАНТИССА УЖЕ ГОТОВА      JL        M0002           ;ИНАЧЕ ЧИСЛО ОЧЕНЬ МАЛО;---- УМЕНЬШЕНИЕ ПОРЯДКА ЧИСЛА ОТ ОЧЕНЬ БОЛЬШОГО ----M0001:FLD       ST              ;РАЗМНОЖИЛИ ЧИСЛО      FXTRACT                   ;РАЗДЕЛИЛИ МАНТИССУ И ПОРЯДОК      FSTP      ST              ;ВКИНУЛИ МАНТИССУ      FIST32P   [ESP]           ;ДОСТАЛИ ПОРЯДОК      CMP       D PTR [ESP],53  ;УЖЕ 53 РАЗРЯДА ?      JLE       M0003           ;ТОГДА МАНТИССА УЖЕ ГОТОВА      FIDIV16   [EDX]           ;РАЗДЕЛИЛИ ЧИСЛО НА ДЕСЯТЬ      JMPS      M0001           ;ПРОВЕРЯЕМ НОВЙ ПОРЯДОК;---- УВЕЛИЧЕНИЕ ПОРЯДКА ЧИСЛА ОТ ОЧЕНЬ МАЛОГО ----M0002:FLD       ST              ;РАЗМНОЖИЛИ ЧИСЛО      FXTRACT                   ;РАЗДЕЛИЛИ МАНТИССУ И ПОРЯДОК      FSTP      ST              ;ВКИНУЛИ МАНТИССУ      FIST32P   [ESP]           ;ДОСТАЛИ ПОРЯДОК      CMP       D PTR [ESP],53  ;УЖЕ 53 РАЗРЯДА ?      JGE       M0003           ;ТОГДА МАНТИССА УЖЕ ГОТОВА      FIMUL16   [EDX]           ;УМНОЖИЛИ ЧИСЛО НА 10      JMPS      M0002           ;ПРОВЕРЯЕМ НОВЙ ПОРЯДОК;---- ВДАЧА МАНТИСС В ДВОИЧНО-ДЕСЯТИЧНОМ ВИДЕ ----M0003:FBSTP     [ESP]+1         ;ЗАПОМНИЛИ МАНТИССУ КАК BCD-ФОРМАТ;---- ФОРМИРОВАНИЕ ТЕКСТА ИЗ BCD-МАНТИСС ----      LEA       EDI,[ESI]+1     ;АДРЕС ОТВЕТА ПОСЛЕ ЗНАКА      FLD1                      ;ЗАГРУЗИЛИ КОНСТАНТУ 1E0      SUB       CL,7            ;ЗАДАННАЯ ДЛИНА МАНТИСС В ОТВЕТЕ      FLD64     [EBX]           ;ОПЯТЬ ЗАГРУЗИЛИ ИСХОДНОЕ ЧИСЛО      LEA       ESI,[ESP]+10    ;АДРЕС ПЕРВХ ЦИФР МАНТИСС      STD      MOV       DH,0            ;ПОКА НУЛИ НЕ ПИШЕМ      FABS                      ;ЗНАК ИСХОДНОГО ЧИСЛА БОЛЬШЕ НЕ НУЖЕН      MOV       AH,0            ;НАЧИНАЕМ С ЧЕТНОЙ ТЕТРАД      FCOM                      ;ЗАРАНЕЕ СРАВНИВАЕМ ЧИСЛО С 1E0      MOV       BL,1            ;ПОКА ОНО МОЖЕТ БТЬ ВБЛИЗИ +1/-1;---- ЦИКЛ ПЕРЕПИСИ ПО ВСЕМ ТЕТРАДАМ BCD-МАНТИСС ----M0004:XOR       AH,1            ;ОЧЕРЕДНАЯ ТЕТРАДА      JZ        @               ;ЕСЛИ НЕЧЕТНАЯ ТЕТРАДА      LODSB                     ;ДОСТАЛИ БАЙТ МАНТИСС      CMP       ESI,ESP         ;ЗА ПРЕДЕЛАМИ МАНТИСС ?      MOV       DL,AL           ;ДВЕ ТЕТРАД МАНТИСС      JB        M0007           ;ЗАКОНЧИЛИ ВВОД;---- ЧЕТНАЯ ТЕТРАДА ----      SHR       AL,4            ;ЧЕТНАЯ ТЕТРАДА BCD      JMPS      M0005;---- НЕЧЕТНАЯ ТЕТРАДА ----@:    MOV       AL,DL      AND       AL,0FH          ;НЕЧЕТНАЯ ТЕТРАДА BCD;---- ПРОПУСК ЛИДИРУЮЩИХ НУЛЕЙ МАНТИСС ----M0005:OR        DH,AL           ;ЕЩЕ ИДУТ НУЛИ МАНТИСС ?      JNZ       @               ;УЖЕ ИДУТ ЦИФР МАНТИСС      INC       ECX             ;НЕ УЧИТВАЕМ ЭТОТ НОЛЬ В МАНТИССЕ      JMPS      M0006           ;ПРОПУСКАЕМ НЕЗНАЧАЩИЙ НОЛЬ;---- ПРОВЕРКА НА ВСЕ ДЕВЯТКИ (Т.Е. НА ЧИСЛО ВБЛИЗИ +1/-1) ----@:    CMP       AL,9            ;ИДУТ СПЛОШНЕ ДЕВЯТКИ ?      JZ        @               ;ДА, ПРОДОЛЖАЮТСЯ ДЕВЯТКИ      MOV       BL,0            ;УЖЕ НЕ ВБЛИЗИ +1/-1 (НЕ ДЕВЯТКА);---- ПРОПУСК ТОЧКИ, ЗАРАНЕЕ ЗАПИСАННОЙ В ОТВЕТ ----@:    CMP       B PTR [EDI],'.' ;ЗДЕСЬ В ШАБЛОНЕ ТОЧКА ?      JNZ       @      INC       EDI             ;ПРОПУСКАЕМ ТОЧКУ;---- ЗАПИСЬ ОЧЕРЕДНОЙ ЦИФР МАНТИСС КАК ТЕКСТА ----@:    ADD       [EDI],AL        ;ПИШЕМ ОЧЕРЕДНУЮ ЦИФРУ В ОТВЕТ      INC       EDI             ;СЛЕДУЮЩИЙ АДРЕСM0006:LOOP     M0004            ;ЗА СЛЕДУЮЩЕЙ ТЕТРАДОЙM0007:CLD;---- ФОРМИРОВАНИЕ ВЕЛИЧИН ПОРЯДКА ----      MOV       ESI,OFFSET ДЕСЯТЬ      FNSTSW    AX      XOR       EDX,EDX         ;ПОКА ПОРЯДОК НОЛЬ      SAHF      JZ        M0011           ;ЧИСЛО СТРОГО РАВНО 1 - ПОРЯДОК НОЛЬ      JA        M0009           ;ЧИСЛО БОЛЬШЕ 1 - ПОРЯДОК ПОЛОЖИТЕЛЕН      MOV       B PTR [EBP]-1,'-' ;ОТМЕТИЛИ ОТРИЦАТЕЛЬНЙ ПОРЯДОК;---- УВЕЛИЧЕНИЕ ПОРЯДКА ДО ЧИСЛА БОЛЬШЕ 1 ----M0008:FIMUL16   [ESI]           ;УВЕЛИЧИЛИ ЧИСЛО В 10 РАЗ      INC       EDX             ;УВЕЛИЧИЛИ ПОРЯДОК      FCOM                      ;СРАВНИВАЕМ С 1      FNSTSW    AX              ;ЗАПОМНИЛИ РЕЗУЛЬТАТ СРАВНЕНИЯ      SAHF      JZ        M0011           ;СТРОГО РАВНО 1 - НАШЛИ ПОРЯДОК      FLD       ST              ;РАЗМНОЖИЛИ ЧИСЛО      FSUB      ST,ST2          ;РАЗНИЦА С 1      FXTRACT                   ;ДОСТАЛИ МАНТИССУ И ПОРЯДОК      FSTP      ST              ;ВБРОСИЛИ МАНТИССУ      FIST32P   [ESP]           ;ПОРЯДОК РАЗНИЦ      CMP       D PTR [ESP],-53 ;УЖЕ ВПЛОТНУЮ К 1 ?      JLE       M0010           ;ДА, ВПЛОТНУЮ, БЛИЖЕ НЕ БУДЕТ      SAHF                      ;ОПЯТЬ ДОСТАЛИ ФЛАГИ СРАВНЕНИЯ С 1      JA        M0011           ;УЖЕ БОЛЬШЕ - НАШЛИ ПОРЯДОК      JMPS      M0008           ;ПРОДОЛЖАЕМ УМНОЖАТЬ НА 10;---- УМЕНЬШЕНИЕ ПОРЯДКА ДО ЧИСЛА МЕНЬШЕ 1 ----M0009:FIDIV16   [ESI]           ;УМЕНЬШИЛИ ЧИСЛО В 10 РАЗ      INC       EDX             ;УМЕНЬШИЛИ ПОРЯДОК      FCOM                      ;СРАВНИВАЕМ С 1      FNSTSW    AX              ;ЗАПОМНИЛИ РЕЗУЛЬТАТ СРАВНЕНИЯ      SAHF      JZ        M0011           ;СТРОГО РАВНО 1 - НАШЛИ ПОРЯДОК      FLD       ST              ;РАЗМНОЖИЛИ ЧИСЛО      FSUB      ST,ST2          ;РАЗНИЦА С 1      FXTRACT                   ;ДОСТАЛИ МАНТИССУ И ПОРЯДОК      FSTP      ST              ;ВБРОСИЛИ МАНТИССУ      FIST32P   [ESP]           ;ПОРЯДОК РАЗНИЦ      CMP       D PTR [ESP],-53 ;УЖЕ ВПЛОТНУЮ К 1 ?      JG        @               ;ЕЩЕ НЕ ВПЛОТНУЮM0010:INC       EBX             ;ПРИЗНАК НАХОЖДЕНИЯ ВБЛИЗИ 1      JMPS      M0011           ;ПЕРЕСТАЕМ ИСКАТЬ ПОРЯДОК@:    SAHF                      ;ОПЯТЬ ЗАГРУЗИЛИ ФЛАГИ СРАВНЕНИЯ С 1      JNB       M0009           ;ПРОДОЛЖАЕМ ДЕЛИТЬ НА 10      DEC       EDX             ;ЧИСЛО В ОТВЕТЕ ДОЛЖНО БТЬ БОЛЬШЕ 1M0011:ADD       ESP,11          ;ОСВОБОДИЛИ СТЕК ОТ BCD-МАНТИСС;---- КОРРЕКТИРОВКА ПОРЯДКА ВБЛИЗИ +/-1 ----      CMP       BL,2            ;БЛИ ВСЕ ДЕВЯТКИ И ПОЧТИ 1 ?      XCHG      EAX,EDX         ;ДОСТАЛИ ЗНАЧЕНИЕ ПОРЯДКА      JNZ       @               ;НЕТ, ЧИСЛО НЕ ВБЛИЗИ 1      DEC       EAX             ;ВБЛИЗИ 1 СВЕРХУ, ДЕЛАЕМ 0.999...E+000      CMP       B PTR [EBP]-1,'-' ;ЧИСЛО МЕНЬШЕ 1E0 ?      JNZ       @               ;НЕТ, БОЛЬШЕ      INC       EAX             ;ВЕРНУЛИ ПОРЯДОК      INC       EAX             ;ВБЛИЗИ 1 СНИЗУ,  ДЕЛАЕМ 9.999...E-001;---- ЗАПИСЬ СТАРШЕЙ ЦИФР ПОРЯДКА ----@:    PUSH      100      XOR       EDX,EDX      POP       EBX             ;ДЕЛИМ НА КОНСТАНТУ 100      FSTP      ST              ;ОЧИЩАЕМ FPU ОТ ПОИСКА ПОРЯДКА      DIV       EBX             ;ПОЛУЧАЕМ ЧАСТНОЕ - ПЕРВУЮ ЦИФРУ      ADD       [EBP],AL        ;СТАРШАЯ ЦИФРА ПОРЯДКА;---- ЗАПИСЬ ДВУХ МЛАДШИХ ЦИФР ПОРЯДКА ----      MOV       BL,10           ;ДЕЛИМ НА КОНСТАНТУ 10      XCHG      EAX,EDX         ;ОСТАТОК ОТ ДЕЛЕНИЯ НА 100      DIV       BL              ;ЧАСТНОЕ И ОСТАТОК - ДВЕ ЦИФР ПОРЯДКА      FSTP      ST              ;ВБРОСИЛИ КОНСТАНТУ 1 ИЗ FPU      ADD       [EBP]+1,AX      ;ДВЕ ОСТАЛЬНЕ ЦИФР ПОРЯДКА;---- ВХОД С ОТВЕТОМ В СТЕКЕ И ДЛИНОЙ СТРОКИ В AL ----      POP       EAX             ;ДЛИНА СТРОКИ ОТВЕТА В СТЕКЕ      RET      DSEGДЕСЯТЬ DW 10                    ;БАЗА ДЛЯ ПЕРЕВОДА В ДЕСЯТИЧНУЮ СИСТЕМУ

Заключение

Использование команд FPU сделало данную реализацию довольно компактной (326 байт команд) и удовлетворительной по скорости. Например, на компьютере с процессором Intel Core Solo 1.33 GHz сто миллионов преобразований числа 1234.567890 в текст заняли 89 секунд.

Таким образом, скорость работы данной подпрограммы составила 845 преобразований в секунду на каждый мегагерц тактовой частоты процессора. При этом полный отказ от использования команд FPU вряд ли позволит увеличить скорость требуемого преобразования.

Однако эффективность преобразования числа в текст могла бы быть повышена, если бы разработчики FPU предусмотрели режим без округления для команды FBSTP. Это позволило бы при применении этой команды для перевода мантиссы в текст избежать циклов умножения и деления на десять лишь только для получения всех значащих цифр.

Подробнее..

Тактическая оптимизация или результаты одного тестирования

28.01.2021 06:11:04 | Автор: admin

Как-то в одном ЖЖ возникло обсуждение работы транслятора IBM для Windows с языка PL/1. Для алгоритмически довольно простого решения стационарного уравнения теплопроводности методом Либмана ответ вообще не удавалось получить, поскольку быстро возникало исключение типа исчезновение порядка (антипереполнение). Мне предложили попробовать решить задачу своим транслятором, изначально разработанным для x86.

Поясню саму эту несложную задачу: матрица T (в примере 5000х5000) значений float первоначально заполняется вся нулями и единицами по краям - верхняя строка и левый столбец. Затем начинается длительный итерационный процесс изменения этой матрицы (N=5000):

DO L=1 TO 10000;   DTM=0;   DO I=2 TO N-1;      DO J=2 TO N-1;         T1=(T(I-1,J)+T(I+1,J)+T(I,J+1)+T(I,J-1))/4;         DTM=MAX(ABS(T1-T(I,J)),DTM);         T(I,J)=T1;      END J;   END I;END L;

Пока я разбирался с задачей, ее заказчик сам нашел простой выход: первоначально заполнять матрицу не нулями, а некоторым исчезающе малым значением 1E-30, тогда антипереполнение пропадает.

Поскольку такие исключения в расчетах у нас бывали и раньше, в системной библиотеке нашего транслятора предусмотрена возможность установки слова управления FPU, в том числе бита UM. Это позволяет выключить исчезновение порядка и заменить его нулевым значением. Поэтому можно было оставить первоначальное состояние матрицы из нулей и единиц.

В результате, и программа, оттранслированная компилятором IBM, и программа, оттранслированная PL/1-KT начали работать.

Задача решилась, но при этом наглядно проявилось отсутствие оптимизации в PL/1-KT. Если результат компилятора IBM позволял проводить одну итерацию в среднем за 227 мс (Intel core i5-3210M 2.50 GHz), то у компилятора PL/1-KT лишь за 624 мс, почти втрое медленнее.

При этом все-таки, небольшая тактическая оптимизация в PL/1-KT имелась. Этот термин я придумал сам, увидев некоторую аналогию с терминами военного дела, где принято различать стратегический, оперативный и тактический уровни.

На мой взгляд, стратегическая оптимизация нахождение лучшего решения в целом (включая и ответ на вопрос, нужно ли вообще искать решение) лежит полностью в компетенции человека. Компилятор не может понять смысл и цель решения. Может быть, когда-нибудь ИИ облегчит и эту область, но пока компиляторам остается только оперативный и тактический уровни.

Оперативная оптимизация это улучшения в терминах программы, а не конкретных команд. Типичный пример вынос общих выражений за пределы цикла.

И, наконец, нижний уровень это тактическая оптимизация в пределах нескольких команд, обычно уже предварительно сгенерированных. Такая оптимизация в большинстве случаев проще оперативной.

Разумеется, имея две небольших программы, выполняющих одни и те же действия, захотелось посмотреть, какая именно оптимизация дает наибольший вклад в быстродействие. Ведь в данном случае для оценки все эти оптимизации легко можно было повторить поэтапно вручную (на ассемблере).

Оптимизатор IBM превратил двумерный массив в одномерный, заменил деление на 4 умножением на 0.25, выровнял данные и подпрограммы на 16, вынес все повторяющиеся действия из циклов. Переменные I и J компилятор IBM вообще не стал заводить, поскольку кроме цикла, они нигде не использовались. В результате в основном цикле работы у него получилось 39 команд, а в случае моего транслятора - 104.

Казалось бы, дальше анализ можно и не проводить, замедление понятно. Но не все оказалось так просто, когда я начал повторять приемы оптимизации.

Например, замена деления умножением, к моему удивлению, совершенно ничего не изменила. Похоже, процессор сам оптимизирует случаи вроде деления на степень двойки.

А вот выравнивание данных на 16 привело не к ускорению, а к замедлению работы. Выравнивание данных здесь вообще выглядит не очень. Я как программист рассчитываю, что для матрицы будет использовано 5000х5000х4=100 000 000 байт, а транслятор IBM реально использовал 400 Мбайт. Для 32-х разрядной Windows это значительная разница.

Ожидаемо самый большой вклад времени всей работы дает запись в массив (чуть ли не половина времени), что ещё более снижает вклад всяких оптимизаций.

Нашлось и место, где оптимизация не сработала. Главное присвоение в программе:

T(I,J)=T1;

транслятором IBM превратилось в:

fld     tbyte ptr [ebp-50h]fstp    tbyte ptr [edx-13890h]

а PL/1-KT в громоздкие:

MOV    EDI,[0040B428] .PIMUL   EAX,[0040B440],00004E20 .IADD    EDI,EAXMOV    EAX,[0040B444] .JLEA    EDI,[EDI+EAX*4]+FFFFB1DCMOV    ESI,0040B42CMOVSD

которые, тем не менее, работают не медленнее, так как не задействуют FPU, а загрузка ESI может выполняться параллельно с EDI.

Но главный вклад в замедление внесли вовсе не такие действия, а то самое включение режима FPU с превращением антипереполнения в чистый ноль. После заполнения матрицы малым значением 1E-30 вместо нулей, одна итерация стала выполняться уже за 343 мс, а не за 624 мс как ранее. Вероятно, сброшенный бит UM слова управления не дает исключению выйти за пределы FPU, но все сопутствующие тормозящие действия остаются и замедляют работу.

Попутно подтвердилось ожидаемое, но печальное обстоятельство. 64-х разрядная кухня работает медленнее 32-х разрядной, чтобы там не писали разработчики этой архитектуры. В данном случае на 10% (365 мс против 343 мс). Я допускаю, что 64-х разрядные команды работают так же быстро, но поскольку часть команд стала длиннее, код получается менее плотным и кэш менее эффективен. Правда, кроме снятия барьера в 4 Гбайт (ради чего и затевалась эта архитектура), тест в 64-х разрядном виде продемонстрировал и дополнительный бонус: работа с double идет так же быстро, как с float, а в 32-х разрядной среде заметно медленнее, хотя бы из-за того, что невозможно одной командой MOVSQ пересылать по 8 байт данных.

Совсем другие результаты показали те же тесты на процессоре AMD A6-9220 RADEON R4 2,50 GHz:

IBM транслятор - 597 мс

PL/1-KT 32 разряда - 616 мс

PL/1-KT 64 разряда - 636 мс

Разница стала незначительной. Но при этом заполнение 1E-30 для ускорения уже не требуется! И без этого заполнения работа получается даже чуть быстрее 610 мс. Таким результатам уже подходит девиз: если результат одинаков, зачем платить больше?.

Возможное объяснение такому времени работы заключается в том, что в AMD FPU-команды выполняются медленнее, чем в Intel по сравнению с остальными командами. Поэтому пока выполняется долгая обработка float, исполнение команд расчета индексов (даже с не вынесенной за цикл частью) успевает закончиться. А может быть, и тип памяти также играет заметную роль.

Подведем некоторые итоги этого тестирования.

1. Небольшая, но все-таки реальная задача показала, что значение оперативной оптимизации снижается. Даже честное вычисление индексов без выноса общих действий за пределы цикла замедлило работу лишь на 50% (343 мс против 227 мс), а для некоторых процессоров и этого нет (в AMD всего на 2%: 610 мс против 597 мс). Отчасти это связано и с тем, что после оптимизации осталось мало независимых команд, которые могут выполняться в параллель, а при отсутствии оптимизации часть независимых команд выполняется процессором параллельно.

2. К сожалению, оптимизация начинает превращаться в некую угадайку и иногда невозможно понять, как реально сработает тот или иной прием, особенно для разных архитектур. Например, в документации Intel я не нашел описания поведения процессора при сброшенном бите UM в слове управления FPU и искренне считал, что никаких дополнительных действий при этом не будет.

3. Даже простая тактическая оптимизация все же тоже дает эффект, например, при использовании SIB-адресации, поскольку части этой адресации декодируются параллельно.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru