Как проверить знак в регистре с плавающей запятой на ассемблере Micropython

Я учу ассемблер для MicroPython (набор инструкций ARM Thumb2 для PyBoard).

Есть ли более быстрый способ проверить знак (положительный / отрицательный) регистра FPU (s0), чем этот?

@micropython.asm_thumb
def float_array_abs(r0, r1):
    label(LOOP)
    vldr(s0, [r0, 0])
    vmov(r2, s0)         # 1
    cmp(r2, 0)           # 2
    itt(mi)              # 3
    vneg(s0, s0)
    vstr(s0, [r0, 0])
    add(r0, 4)
    sub(r1, 1)
    bgt(LOOP)

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

ОБНОВЛЕНИЕ 1:

Основываясь на комментариях (спасибо), я улучшил скорость кода:

@micropython.asm_thumb
def float_array_abs1(r0, r1):
    label(LOOP)
    ldr(r2, [r0, 0])
    cmp(r2, 0)         # this works for some reason
    bge(SKIP)
    vmov(s0, r2)
    vneg(s0, s0)
    vstr(s0, [r0, 0])  # this can be skipped if not negative
    label(SKIP)
    add(r0, 4)
    sub(r1, 1)
    bgt(LOOP)

Но это все еще оставляет вопрос, является ли это надежным способом определения знака значения FP?

Для справки здесь представлены байтовые представления четырех значений с плавающей запятой в моей системе:

-1.0 0xbf800000
-0.0 0x80000000
 0.0 0x00000000
 1.0 0x3f800000

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

Я думаю, что это может быть "правильный" способ сделать это (то есть правильное сравнение FPU):

def float_array_abs2(r0, r1):
    mov(r2, 0)
    vmov(s1, r2)
    label(LOOP)
    vldr(s0, [r0, 0])
    vcmp(s0, s1)
    vmrs(APSR_nzcv, FPSCR)
    itt(mi)
    vneg(s0, s0)
    vstr(s0, [r0, 0])
    add(r0, 4)
    sub(r1, 1)
    bgt(LOOP)

Но я рассчитал это, и это на 11% медленнее, чем код выше (float_array_abs1). Поэтому было бы неплохо использовать предыдущий код, если это надежное решение.

ОБНОВЛЕНИЕ 2:

@ Ped7g предложил метод and 0x7FFFFFFF (см. комментарии).

Я проверил это, и это работает. Вот код:

@micropython.asm_thumb
def float_array_abs3(r0, r1):
    movwt(r3, 0x7FFFFFFF)
    label(LOOP)
    ldr(r2, [r0, 0])
    and_(r2, r3)
    str(r2, [r0, 0])
    add(r0, 4)
    sub(r1, 1)
    bgt(LOOP)

ИСПРАВЛЕНИЕ: это быстрее, чем float_array_abs1 выше. Это, кажется, лучшее решение, но надежно ли это?

1 ответ

Маскирование знака бит до 0 с and является безопасным и оптимальным для двоичных форматов с плавающей запятой IEEE 754, таких как float а также double,

Он преобразует -Inf в +Inf по желанию. Это будет конвертировать -NaN в +NaN, но это все еще NaN.

NaN обозначается единичным показателем и ненулевым значением and. Inf - единичный показатель с нулевым значением and. ( https://en.wikipedia.org/wiki/Single-precision_floating-point_format)

Большая часть кода не заботится о полезной нагрузке или признаке NaN, просто это NaN, поэтому очистка бита знака - это хорошо.


ARM может сделать это с помощью целочисленных инструкций SIMD NEON для 4 поплавков одинарной точности одновременно. Я не знаю, поддерживает ли VFP (не-NEON аппаратный FPU) инструкцию AND.

Связанный: Самый быстрый способ вычисления абсолютного значения с использованием SSE AND - это лучший способ и для x86.


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

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

Другие вопросы по тегам