Посмотрев на таблицу регистров в архитектуре x86 / x64, я заметил, что есть целый раздел из 128, 256 и 512-битных регистров, которые я никогда не видел, чтобы они использовались в сборке или декомпилированном коде C / C ++: XMM (0-15) для 128, YMM (0-15) для 256, ZMM (0-31) 512.

Немного покопавшись, я понял, что вам нужно использовать 2 64-битные операции для вычисления 128-битного числа, вместо использования общих add, sub, {{ X2}}, div операций. Если это так, то в чем именно заключается смысл использования этих расширенных наборов регистров и есть ли какие-либо операции сборки, которые вы можете использовать для управления ими?

8
Draden Merenox 22 Окт 2018 в 18:09

2 ответа

Лучший ответ

Они используются в

  • Операции с плавающей точкой
  • Операции с несколькими данными одновременно

вам нужно использовать 2 64-битные операции, чтобы выполнить математику над 128-битным числом

Нет, они не предназначены для этой цели, и вы не можете легко использовать их для 128-битных чисел. Намного быстрее добавить 128-битное число всего с двумя инструкциями: add rax, rbx; adc rdx, rcx вместо тонны инструкций при работе с регистрами XMM. Видеть


Что касается их использования, во-первых, они используются для скалярных операций с плавающей запятой . Поэтому, если у вас есть float или double в C или C ++, то они, скорее всего, будут храниться в младшей части регистров XMM и управляться инструкциями, заканчивающимися на ss ( scalar одинарный ) или sd ( скалярный двойной )

Фактически, есть еще один набор из восьми 80-битных регистров ST(x), который был доступен с x87 co -процессор для вычислений с плавающей запятой. Однако они медленные и менее предсказуемые. Медленно, потому что по умолчанию операции выполняются с более высокой точностью, что по сути требует дополнительной работы , а также требует, чтобы хранилище было загружено для округления до более низкой точности , если необходимо. Непредсказуемо также из-за высокой точности. Сначала это может показаться странным, но это легко объяснить, например, некоторые операции переполнены или потеряны с точностью float или double, но не с точностью long double. Это вызывает множество ошибок или неожиданных результатов в 32- и 64-битной сборке 1

Вот пример с плавающей запятой для обоих наборов регистров

// f = x/z + y*z
x87:
        fld     dword ptr [esp + 12]
        fld     st(0)
        fdivr   dword ptr [esp + 4]
        fxch    st(1)
        fmul    dword ptr [esp + 8]
        faddp   st(1)
        ret
SSE:
        divss   xmm0, xmm2
        mulss   xmm1, xmm2
        addss   xmm0, xmm1
        ret
AVX:
        vdivss  xmm0, xmm0, xmm2
        vmulss  xmm1, xmm1, xmm2
        vaddss  xmm0, xmm0, xmm1
        ret

Переход на более быстрые и согласованные регистры SSE является одной из причин, по которой тип 80-битной расширенной точности long double не подходит для больше доступен в MSVC


Затем Intel представила набор инструкций MMX для SIMD, в которых используются те же регистры ST(x) с новым именем MMX. MMX может означать Multiple Math eXtension или Matrix Math eXtension , но IMHO, скорее всего, или MultiMedia eXtension , поскольку мультимедиа и Интернет становятся все более важными в это время. В мультимедийных решениях вам очень часто приходится выполнять одни и те же операции с каждым пикселем, текселем, звуковым образцом ... например,

for (int i = 0; i < 100000; ++i)
{
   A[i] = B[i] + C[i];
   D[i] = E[i] * F[i];
}

Вместо того, чтобы работать с каждым элементом отдельно, мы можем ускорить выполнение нескольких элементов за раз. Вот почему люди изобрели SIMD. С MMX вы можете увеличить яркость 8 пиксельных каналов или громкость сразу четырех 16-битных звуковых сэмплов ... Операции с одним элементом называются scalar, а полный регистр называется вектором, который представляет собой набор скалярных значений.

Из-за недостатков MMX (например, повторного использования регистров ST или отсутствия поддержки с плавающей запятой) при расширении набора инструкций SIMD с помощью Streaming SIMD Extensions (SSE) Intel решила предоставить им совершенно новый набор регистров с именем XMM, который вдвое длиннее (128 бит), поэтому теперь мы можем работать сразу с 16 байтами. Кроме того, он поддерживает сразу несколько операций с плавающей запятой. Затем Intel расширила XMM до 256-битного YMM в Advanced Vector Extensions (AVX) и удвоила длина еще раз в AVX-512 (на этот раз также увеличилось количество регистров до 32 в 64-битном режиме). Теперь вы можете работать с шестнадцатью 32-битными целыми числами за раз.

Из вышесказанного вы можете понять вторую и наиболее важную роль этих регистров: выполнение операций с несколькими данными параллельно с помощью одной инструкции . Например, в SSE4 набор были введены инструкции по работе со строками C. Теперь вы можете подсчитывать длину строки, находить подстроки ... намного быстрее, проверяя сразу несколько байтов. Вы также можете копировать или сравнивать память намного быстрее. Современные реализации memcpy перемещают 16, 32 или 64 байта за раз, в зависимости от наибольшей ширины регистра, а не один за другим, как в простейшем решении C.

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

Из-за важности SIMD практически любые высокопроизводительные архитектуры в настоящее время имеют свою собственную версию SIMD, например Altivec на PowerPC или Neon на ARM.


1

10
phuclv 25 Окт 2018 в 04:40

Эти регистры являются частью расширений наборов команд SSE, AVX и AVX512. Ваш компилятор C должен, по крайней мере, использовать нижние 64 бита из них для операций с плавающей запятой, как это указано в ABI.

Эти регистры представляют собой регистры SIMD (одна инструкция и несколько данных), которые в основном используются для высокопроизводительного кода. Процессор поддерживает специальные инструкции SIMD, которые могут обрабатывать несколько данных одновременно, занимая столько времени, сколько обычно требуется для обработки одного элемента данных. Большая часть кода, использующего эти регистры, написана на ассемблере или с использованием специальных встроенных функций , поскольку компиляторы плохо умеют использовать инструкции SIMD сами по себе. Улучшение компиляторов в этом (оптимизация под названием автоматическая векторизация ) - активная область исследований.

В качестве примера предположим, что программа хочет выполнить матричное умножение чисел с плавающей запятой двойной точности. С регистром AVX с ymm0 по ymm15 одновременно можно обрабатывать 4 числа, что ускоряет алгоритм в 4 раза по сравнению с обычной реализацией. Это большая разница.

Обратитесь к справочнику по набору команд для инструкций, использующих эти регистры. На этом веб-сайте перечислены все они в доступной форме. Если вы хотите их использовать, я предлагаю вам использовать встроенные функции, поскольку они немного проще в использовании, чем сборка.

4
fuz 22 Окт 2018 в 15:47
52932539