Почему это так медленно, чтобы получить доступ к отдельным элементам SIMD - PullRequest
0 голосов
/ 30 августа 2018

Я узнаю о внутренностях SIMD в C ++, и я немного запутался. Скажем, у меня есть __m128, и я хочу получить доступ к его первому элементу с помощью __m128.m128_f32 [0] (я знаю, что это реализовано не для всех компиляторов), почему это делается, предположительно, очень медленно. Разве это не просто чтение памяти, как любой другой? Я читал некоторые другие страницы, где упоминались такие вещи, как Load-Hit-Store, но я не понял этого в контексте своего вопроса. Я знаю, что делать что-то вроде этого неуместно, и я не собираюсь этого делать, но мне любопытно, что на самом деле делает это настолько медленным.

1 Ответ

0 голосов
/ 30 августа 2018

Векторные переменные SIMD обычно находятся в регистрах XMM, а не в памяти. Хранение вектора / скалярная перезагрузка - одна из стратегий компилятора для реализации чтения целочисленного элемента вектора, но определенно не единственного. И обычно это не лучший выбор.

Смысл этого совета в том, что если вы хотите получить горизонтальную сумму, напишите ее с помощью встроенных функций shuffle / add, вместо того, чтобы обращаться к элементам и заставить компилятор производить, вероятно, худший asm, чем вы получили бы из правильно выбранных случайных чисел. См. Самый быстрый способ сделать горизонтальную векторную сумму с плавающей запятой на x86 для реализаций C с генерируемым компилятором asm.


Запись в элемент вектора через память будет хуже, потому что векторное хранилище / перекрывающийся скалярный магазин / перезагрузка вектора вызовут остановку пересылки магазина. Но вместо этого компиляторы не настолько глупы, и могут использовать movd xmm0, eax и использовать векторное перемешивание для объединения нового элемента в вектор.

Ваш конкретный пример чтения __m128.m128_f32[0] не очень хороший: он буквально бесплатен, потому что скаляр float обычно хранится в младшем элементе регистра XMM (если вы не компилируете 32-битный код с устаревшим x87). с плавающей точкой для скаляров). Таким образом, нижний элемент вектора __m128 в регистре XMM уже - это скалярное число с плавающей запятой, которое компилятор может использовать с инструкциями addss. Соглашения о вызовах передаются float в регистрах XMM и не требуют обнуления верхних элементов, поэтому там нет никаких дополнительных затрат.


На x86 это не катастрофически дорого, но вы определенно хотите избежать этого во внутренних циклах. Что касается float, хороший компилятор превратит его в случайные числа, которые вы могли бы написать сами с помощью встроенных функций, которые в конечном итоге выполняют float _mm_cvtss_f32 (__m128 a) (что компилируется в ноль инструкций, как описано выше).

Для целых чисел, с SSE4.1, мы надеемся, вы получите pextrd eax, xmm0, 3 или что-то еще (или более дешевый movd eax, xmm0 для младшего элемента).


В ARM передачи между целочисленным и векторным регистрами намного дороже, чем в x86 . По крайней мере, большая задержка, если не плохая пропускная способность. В некоторых процессорах ARM целочисленная и векторная части ЦП вообще не связаны друг с другом, и возникают задержки, когда одной стороне приходится ждать результата от другой. (Я думаю, что я читал, что последние ARM, такие как процессоры, которые поддерживают AArch64, обычно имеют намного меньшую задержку int <-> SIMD.)

(вы не пометили x86 или SSE, но упомянули __m128 для MSVC, поэтому я в основном отвечал за x86.

...