Оптимизация x64 ассемблера для цикла MUL - PullRequest
21 голосов
/ 14 ноября 2011

Я пишу математический код, который должен быстро умножать большие числа.Он разбивается на умножения массива целых чисел с одним целым числом.В C ++ это выглядит так (для unsigned's):

void muladd(unsigned* r, const unsigned* a, unsigned len, unsigned b) {
   unsigned __int64 of = 0;  // overflow
   unsigned i = 0;  // loop variable
   while (i < len) {
      of += (unsigned __int64)a[i] * b + r[i];
      r[i] = (unsigned)of;
      of >>= 32;
      ++i;
   }
   r[i] = (unsigned)of;  // save overflow
}

Я развернул этот цикл вручную, преобразовал его в 64-разрядный и работал над выводом компилятора .asm для дальнейшей его оптимизации.Основной цикл .asm теперь выглядит следующим образом:

mov   rax, rdi                             ; rdi = b
mul   QWORD PTR [rbx+r10*8-64]             ; rdx:rax = a[i] * b; r10 = i
mov   rsi, QWORD PTR [r14+r10*8-64]        ; r14 = r; rsi = r[i]
add   rax, rsi
adc   rdx, 0
add   rax, r11                             ; r11 = of (low part)
adc   rdx, 0
mov   QWORD PTR [r14+r10*8-64], rax        ; save result
mov   r11, rdx

; this repeats itself 8 times with different offsets

Когда я проверяю это, я обнаруживаю, что для моего умножения на Core2 Quad требуется около 6,3 цикла на среднее значение для умножения.

Мой вопрос: могу ли я как-нибудь ускорить это?К сожалению, я не вижу способа избежать одного из дополнений, и для умножения всегда нужен RDX: RAX, поэтому мне нужно перемещать данные и не могу что-то «умножить параллельно».

Любые идеи кто-нибудь?

Обновление: После еще одного тестирования мне удалось довести скорость до примерно 5,4 цикла на 64-битный MUL (включая все операции добавления, перемещения и зацикливания).Я думаю, это лучшее из того, что вы можете получить на Core2, поскольку у Core2 нет очень быстрой инструкции MUL: она имеет пропускную способность 3 и задержку 6 (соответственно 7) циклов.Песчаный мост будет намного лучше с пропускной способностью 1 и задержкой в ​​3 (соответственно 4) цикла.

Что касается гораздо меньшего числа для GMP: я получил это из их исходного кода, и мне кажется, чтоэто теоретическое число.Но точно то, что это число рассчитано для процессора AMD K9.И из того, что я прочитал, я понимаю, что у AMD более быстрый модуль MUL, чем у (старых) чипов Intel.

Ответы [ 4 ]

1 голос
/ 28 февраля 2012

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

Я бы попробовал предварительно выбрать [i] и r [i]

при использовании gcc используйте инструкцию __builtin_prefetch () или PREFETCHT0 в ассемблере

http://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Other-Builtins.html

Когда это работает, результаты могут быть впечатляющими,Поскольку цикл длится примерно тысячу итераций, я бы предварительно выбрал a [i + 64] и r [i + 64] в качестве отправной точки и посмотрел бы, как сильно это влияет на ваш процессор.Возможно, вам придется попробовать большее расстояние для предварительной выборки.

0 голосов
/ 29 февраля 2012

Содержит ли r что-то важное перед вызовом?

Если это так, и вы накапливаете на нем деньги, прекратите чтение.

Если этого не произойдет (то есть вывсегда накапливаются в нули), и, если вы вызываете эту функцию для массивов, значительно превышающих размеры кеша, я бы искал способ устранить необходимость чтения из r и преобразования «результата сохранения» MOV до MOVNT (_mm_stream_ps по внутренним признакам).

Это может значительно улучшить производительность.Как ?В настоящее время ваши кэши извлекают строки кэша из a, выбирают строки кэша из r и записывают строки кэша обратно в r.В так называемых потоковых хранилищах вы просто извлекаете строки кэша из a и сквозной записи прямо в r: гораздо меньше трафика шины.Если вы посмотрите на любую современную реализацию memcpy CRT, она переключится на использование потоковых хранилищ выше некоторого порога, связанного с размером кэша (и запустит почти в два раза быстрее , чем memcpy, используя обычные шаги).

0 голосов
/ 18 февраля 2012

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

0 голосов
/ 14 ноября 2011

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

...