Генерация фракталов с помощью SSE/SSE2 - Метод FFFF

ОГЛАВЛЕНИЕ

Метод FFFF

FFFF v3.2.2, вышеназванный генератор набора Мандельброта, использует другой метод. FFFF имеет открытый исходный код, поэтому публикация его кода здесь полностью правомерна:

; xmm0 = zx  xmm1 = zy  xmm2 = tmp  xmm3 = tmp  xmm4 = px  
;            xmm5 = py  xmm6 = результат xmm7 = 4.0
; eax = tmp    ecx = счётчик цикла
   MOVAPS xmm0,xmm4
   XORPS  xmm6,xmm6
   MOVAPS xmm1,xmm5
   mov ecx, ITER
ILOOP:
   ; xmm0=zx      xmm1=zy
   MOVAPS xmm2, xmm0
   MULPS  xmm2, xmm1
   MULPS  xmm0, xmm0
   MULPS  xmm1, xmm1
   ; xmm0=zx^2    xmm1=zy^2  xmm2=zx*zy
   addps xmm2, xmm2
   movaps xmm3, xmm0
   addps xmm3, xmm1
   ; xmm0=zx^2    xmm1=zy^2  xmm2=2*zx*zy  xmm3=zx^2+zy^2
   cmpltps xmm3, xmm7
   movmskps eax, xmm3
   test eax, eax
   jz EXIT

   subps xmm0, xmm1
   movaps xmm1, xmm2
   ; xmm0=zx^2-zy^2  xmm1=2*zx*zy   xmm3=zx^2+zy^2
   ANDPS  xmm3, xmm7 ; xmm6 += (xmm3<радиус) ? 4.0 : 0.0
   ADDPS  xmm6, xmm3
   ADDPS  xmm0, xmm4
   ADDPS  xmm1, xmm5
   dec ecx
   jnz ILOOP
EXIT:

FFFF пытается сравнить x0^2 + x1^2 с 4.0, и только если сравнение не удается, находит новые значения для x0 и x1. Если сравнение возвращает истину, цикл прерывается, и нет нужды вычислять x0 и x1. Хотя такая стратегия может казаться хорошей, эксперименты показывают, что это не так. Посмотрите на длинную цепь: MOVAPS xmm3, xmm0; ADDPS xmm3, xmm1; CMPLTPS xmm3, xmm7; MOVMSKPS eax, xmm3. При выполнении этой цепи есть много возможностей использовать свободные блоки выполнения и параллельно выполнить вторую цепь. Но авторы FFFF не вставили никаких команд между командами этой цепи. Планировщик вынужден сам переупорядочивать команды. Иногда он терпит неудачу, и блоки выполнения остаются неиспользованными. Например, в коде между MULPS xmm1, xmm1 и SUBPS xmm0, xmm1 столь большое расстояние, что вторая команда может не попасть в очереди вовремя. В таком случае между командами добавляется дополнительная задержка. То же самое касается ADDPS xmm2, xmm2 и MOVAPS xmm1, xmm2: маловероятно, что они выполнятся без задержки.

Схема показывает идеальный случай вообще без задержек. Даже в таком случае на вычисление x0 требуется 20 тактовых циклов, а на вычисление x1 требуется 27 тактовых циклов. Для метода MOV-ADD цифры - 16 и 22 тактовых циклов, соответственно. Критический путь (отмеченный темно-серым цветом) включает в себя две команды MOVAPS, имеющие долгие задержки, тогда как метод MOV-ADD использует только одну MOVAPS на своем критическом пути.

Есть дополнительные задержки перед MOVAPS x1, x2 и MOVAPS x3, x0, потому что предыдущая команда выполнилась в другом блоке. ADDPS x3, x1 откладывается на один цикл из-за ограниченной производительности сумматора FP.

Во всем остальном MOV-ADD схож с FFFF. Оба метода используют SSE для вычисления значений 4 пикселей вместе, оба имеют одинаковое ограничение точности и одинаковый эффект дергания при масштабировании. Был использован схожий алгоритм, но команды в нем были расположены иначе для повышения производительности. Другие различия между FFFF и приведенной программой будут показаны позже.

В коде FFFF выше имена регистров изменены на имена, используемые в MOV-ADD (xmm0 для zx, xmm1 для zy, и так далее). Это изменение не влияет на производительность, но облегчает сравнение кода методов.