Intel указывает в своем «Справочном руководстве по оптимизации архитектуры», что CVTSI2SD
имеет задержку в 3-4 цикла (и 1 цикл) на базовой линии для настольных компьютеров и серверов с Core2. Это можно считать хорошим примером.
С точки зрения аппаратного обеспечения такое преобразование требует некоторой помощи, которая делает его пригодным для разумного количества циклов, в противном случае оно становится слишком дорогостоящим. Ниже следует наивное, но довольно хорошее объяснение. Во всяком случае, я предполагаю, что одного тактового цикла процессора достаточно для такой операции, как целочисленное целочисленное добавление (но не радикально длиннее!), И все результаты предыдущего цикла применяются на границе цикла.
Первый тактовый цикл с соответствующей аппаратной поддержкой (priority encoder) дает Count Leading Zeros (CLZ), среди которых обнаруживаются два особых случая: 0 и INT_MIN (набор MSB и все остальные бит очищены). 0 и INT_MIN лучше обрабатывать отдельно (загрузка константы в регистр назначения и завершение). В противном случае, если целое число ввода было отрицательным, оно должно быть отменено; для этого обычно требуется еще один цикл (потому что отрицание представляет собой комбинацию инверсии и добавления бит переноса). Таким образом, проводятся 1-2 цикла.
В то же время он может рассчитывать смещенное предсказание экспоненты на основе результата CLZ. Обратите внимание, что нам не нужно заботиться о денормализованных значениях или бесконечности. (Можно ли предсказать CLZ (-x) на основе CLZ (x), если x < 0? Если мы сможем, это сэкономит нам 1 цикл.)
Затем применяется сдвиг (1 цикл снова, с barrel shifter) до поместите целочисленное значение, чтобы его наивысшее значение 1 находилось в фиксированном положении (например, со стандартными 3 битами расширения и 24-битной мантиссой, это бит № 26).Это использование баррель-переключателя должно сочетать все младшие бит с липким битом (может потребоваться отдельный пользовательский экземпляр shrelter ствола, но это waaaay дешевле, чем кеш-мегабайт или OoO dispatcher). Теперь, до 3 циклов.
Затем применяется округление. Округление анализирует в нашем случае 4 наименьших бита текущего значения (mantissa LSB, guard, round и sticky) и OTOH, текущий режим округления и целевой знак (извлеченный в цикле 1). Округление до нуля (RZ) приводит к игнорированию защитных/круглых/липких бит. Округление до -∞ (RMI) для положительного значения и + ∞ (RPI) для отрицательного значения равно нулю. Округление до ∞ противоположного знака приводит к добавлению 1 к основной мантиссе. Наконец, округление до ближайшего к узлу (RNE): x000 ... x011 -> discard; x101 ... x111 -> добавить 1; 0100 -> отказаться; 1100 -> добавить 1. Если аппаратное обеспечение достаточно быстро, чтобы добавить этот результат в тот же цикл (я думаю, это вероятно), у нас есть до 4 циклов.
Это добавление на предыдущем этапе может привести к переносу (например, 1111 -> 10000), поэтому экспоненту может увеличиться. Заключительный цикл состоит в том, чтобы упаковать знак (из цикла 1), мантисса (до знака) и смещенный показатель (рассчитанный по циклу 2 из результата CLZ и, возможно, отрегулированный с переносом из цикла 4). Итак, 5 циклов.
Является ли преобразование из int в плавание, как правило, более дешевым, чем float to int?
Мы можем оценить одно и то же преобразование, например. от binary32 до int32 (подписано). Предположим, что преобразование NaN, INF или слишком большого значения приводит к фиксированному значению, скажем, INT_MIN (-2147483648). В этом случае:
Разделить и проанализировать входное значение: знак S; BE - предвзятый показатель; M - мантисса (значение); также примените режим округления. Сигнал «преобразование невозможно» (переполнение или недействительный) генерируется, если: BE> = 158 (сюда входят NaN и INF). Сигнал «нуля» генерируется, если BE < 127 (abs (x) < 1) и {RZ, или (x> 0 и RMI), или (x < 0 и RPI)}; или, если BE < 126 (abs (x) < 0,5) с RNE; или, BE = 126, значение = 0 (без скрытого бита) и RNE. В противном случае сигналы для окончательных +1 или -1 могут быть сгенерированы для случаев: BE < 127 и: x < 0 и RMI; x> 0 и RPI; BE = 126 и RNE. Все эти сигналы могут быть рассчитаны в течение одного цикла с использованием логической логической схемы и привести к завершению результата в первом цикле. Параллельно и независимо вычислять 157-BE с использованием отдельного сумматора для использования в цикле 2.
Если еще не финализировано, мы имеем abs (x)> = 1, поэтому BE> = 127, но BE < = 157 (поэтому abs (x) < 2 ** 31). Получите 157-BE из цикла 1, это необходимое количество сдвига. Примените сдвиг вправо на эту величину, используя тот же самый баррек, что и в алгоритме int -> float, к значению с (еще раз) 3 дополнительными битами и сбором липкого бита. Здесь проводятся 2 цикла.
Применить округление (см. Выше). Проводится 3 цикла, а перенос может производиться. Здесь мы можем снова обнаружить целочисленное переполнение и получить соответствующее значение результата. Забудьте дополнительные биты, теперь оценивается только 31 бит.
Наконец, отрицайте полученное значение, если x было отрицательным (знак = 1). Проводится до 4 циклов.
Я не опытный разработчик бинарной логики, поэтому может упустить шанс скомпрометировать эту последовательность, но она довольно близка к значениям Intel. Таким образом, сами конверсии довольно дешевы, при условии наличия аппаратной поддержки (говоря еще раз, это приводит не более чем к нескольким тысячам ворот, так что крошечный для современного производства чипов).
Вы также можете взглянуть на Berkeley Softfloat library - он реализует практически тот же подход с незначительными модификациями. Начните с исходного файла ui32_to_f32.c
.Они используют больше дополнительных бит для промежуточных значений, но это не принципиально.
На некоторых архитектурах данные с плавающей запятой и целочисленные данные хранятся в физически отдельных файлах регистров. Целочисленный операнд в регистре не может быть передан/преобразован непосредственно в операнд с плавающей запятой в регистр; вместо этого процесс включает сначала хранение целочисленных данных в памяти, затем чтение из памяти и преобразование в плавающую точку. Это может привести к замедлению (er), даже если процессор использует пересылку store-to-load. То же самое относится к передаче в обратном направлении (с плавающей запятой, преобразованной в целое), что может вызвать дополнительные * проблемы, как вы уже отметили. – njuffa
@njuffa: Не влияет ли эффект, который вы упомянули, на 'reinterpret_cast', а не на преобразование? –
@BenVoigt я имел в виду * конвертация *. Например, на классическом x86 с x90 FPU вам понадобится что-то вроде этого: 'mov eax, dword ptr [tmp]; fld dword ptr [tmp] ', чтобы преобразовать 32-разрядное целое число со знаком в целочисленный регистр в значение с плавающей запятой в регистре FPU. – njuffa