Ответ 1
В моем вопросе были две проблемы с моей функцией muldws1_improved
. Один из них заключается в том, что он пропустил перенос, когда я сделал xl*yh + xh*yl
. Вот почему он не прошел единичные тесты. Но другое заключается в том, что есть подписанные * неподписанные продукты, для которых требуется гораздо больше машинной логики, чем это видно в коде C. (см. Мое правление ниже). Я нашел лучшее решение, которое сначала оптимизировало функцию unsigned product muldwu1, а затем сделать
muldwu1(w,x,y);
w[0] -= ((x<0) ? y : 0) + ((y<0) ? x : 0);
чтобы исправить знак.
Вот моя попытка улучшить muldwu1
, используя нижнее слово lo = x*y
(да, эта функция проходит модульные тесты от восторга хакеров).
void muldwu1_improved(uint32_t w[], uint32_t x, uint32_t y) {
uint16_t xl = x; uint16_t xh = x >> 16;
uint16_t yl = y; uint16_t yh = y >> 16;
uint32_t lo = x*y; //32x32 to 32
uint32_t t1 = xl*yh; //16x16 to 32
uint32_t t2 = xh*yl; //16x16 to 32
uint32_t t3 = xh*yh; //16x16 to 32
uint32_t t = t1 + t2;
uint32_t tl = 0xFFFF & t;
uint32_t th = t >> 16;
uint32_t loh = lo >> 16;
uint32_t cy = ((t<t1) << 16) + (loh<tl); //carry
w[1] = lo;
w[0] = t3 + th + cy;
}
Это использует одно меньшее дополнение, чем оригинальная функция от восторга хакеров, но он должен делать два сравнения
1 mul32x32 to 32
3 mul16x16 to 32
4 add32
5 shift logical (or shuffles)
1 and
2 compare32
***********
16 operations
Edit:
Меня беспокоило выражение в Hacker Delight (2-е издание), в котором говорится о алгоритмах mulhs и mulhu.
Алгоритм требует 16 основных команд RISC либо в подписанной, либо без знаковой версии, четыре из которых являются умножениями.
Я реализовал беззнаковый алгоритм в только 16 инструкциях SSE, но моя подписанная версия потребовала дополнительных инструкций. Я понял, почему, и теперь я могу ответить на свой вопрос.
Причина, по которой я не смог найти лучшую версию, которая в Hacker Delight заключается в том, что их гипотетический RISC-процессор имеет инструкцию, которая вычисляет нижнее слово произведения двух слов. Другими словами, их алгоритм уже оптимизирован для этого случая, и поэтому вряд ли существует лучшая версия, чем та, которая у них уже есть.
Причина, по которой они перечисляют альтернативу, состоит в том, что они предполагают, что умножение (и деление) может быть более дорогостоящим, чем другие инструкции, и поэтому они оставили альтернативу в качестве случая для оптимизации.
Таким образом, код C не скрывает значительную машинную логику. Он предполагает, что машина может использовать слово word для более низкого слова.
Почему это имеет значение? В своем алгоритме они делают сначала
u0 = u >> 16;
и позже
t = u0*v1 + k;
если u = 0x80000000
u0 = 0xffff8000
. Однако, если ваш процессор может принимать только половинные слова для получения полного слова, верхняя половина слова u0
игнорируется, и вы получаете неправильный подписанный результат.
В этом случае вы должны вычислить неподписанное верхнее слово, а затем исправить с помощью hi -= ((x<0) ? y : 0) + ((y<0) ? x : 0);
, как я уже сказал.
Причина, по которой меня это интересует, заключается в том, что команда Intel SIMD (SSE2 через AVX2) не имеет инструкции, которая делает 64x64 до 64, у них только 32x32 до 64. Поэтому моя подписанная версия требует больше инструкций.
Но AVX512 имеет инструкцию 64x64 - 64. Поэтому с AVX512 подписанная версия должна принимать такое же количество инструкций, как и без знака. Однако, поскольку команда 64x64 - 64 может быть намного медленнее, чем инструкция 32x32 - 64, все равно имеет смысл делать неподписанную версию в любом случае, а затем исправлять.