AVX2 MaskLoad/MaskStore ushorts?

В качестве эксперимента я пытался выяснить, как выполнить загрузку/сохранение маски с помощью ushorts, превышающего фактическое требование.

Не существует вызова API для MaskLoad/MaskStore, который принимает указатель на ushort, поэтому я подумал, что вам, возможно, придется просто сделать это с указателем int?, что я и попытался сделать ниже.

Что касается TestUShort, я установил смещение от начала массива, чтобы пропустить первые два ushorts.

ushort* address = ptr + 2;

Затем я создал маску,

[0xFFFF0000, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF]

который, как я думал, будет вести себя следующим образом (X — включить, 0 — замаскировать)

[X,0, X,X, 0,0, X,X, 0,0, X,X, X,X, X,X]

Поэтому я ожидаю

0, 0 <-- (first two skipped) ...12, 0, 12, 12, 0, 0, 12, 12, 0, 0, 12, 12, 12, 12, 12, 12

но вместо этого я получаю:

0, 0 <-- (first two skipped) ...12, 12, 12, 12, 0, 0, 12, 12, 0, 0, 12, 12, 12, 12, 12, 12

Это потому, что вы можете маскировать только блоки по 4 байта? Есть ли способ сделать это? Меня также интересует влияние такой операции на производительность на ushorts со смещением и маской, которая, как я полагаю, будет очень дорогостоящей из-за невыравнивания? Поскольку код в настоящее время не работает, я еще не тестировал его, поскольку никакие тайминги не имеют реального значения, если код не работает.

  static unsafe void TestUShort()
  {
      ushort[] values = new ushort[256];
      Vector256<uint> mask = Vector256.Create<uint>([0xFFFF0000, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF]);
      Vector256<ushort> add = Vector256.Create<ushort>(12);

      fixed (ushort* ptr = values)
      {
          ushort* address = ptr + 2;
          uint* addressInt = (uint*)address;

          var v = Avx2.MaskLoad(addressInt, mask).As<uint, ushort>();
          v = Avx2.AddSaturate(v, add);

          Avx2.MaskStore(addressInt, mask, v.As<ushort, uint>());
      }
  }

  static unsafe void TestUInt()
  {
      uint[] uintValues = new uint[256];

      Vector256<uint> mask = Vector256.Create<uint>([0xFFFFFFFF, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF]);
      Vector256<uint> add = Vector256.Create<uint>(12);

      fixed (uint* ptr = uintValues)
      {
          var v = Avx2.MaskLoad(ptr, mask);
          v = Avx2.Add(v, add);

          Avx2.MaskStore(ptr, mask, v);
      }
  }
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не существует инструкции AVX2, которая непосредственно выполняет замаскированное сохранение слов. Вы не можете использовать для этого хранилище в маске dword, потому что:

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

MASKMOVDQU (Sse2.MaskMove) может выполнять маскированное сохранение на побайтовой основе, однако он использует вневременную подсказку, и это может быть хорошо (предположительно) именно в том случае использования, для которого оно предназначено. (по крайней мере, в руководстве говорится, что это для чего-то полезно, на самом деле я никогда не использовал его успешно), в обычных случаях это в большинстве случаев плохо. Например, в Zen 3 это занимает 75 мкс и может выполняться только один раз каждые 18 тактов. Кроме того, это всего лишь 128-битная операция, поэтому вам понадобится две из них.

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

  • В отличие от правильного замаскированного хранилища, прикосновение к недействительной странице со «смешанным хранилищем» приведет к ошибке страницы. Это делает его плохо подходящим для обработки частичного вектора, который может находиться в конце массива. В зависимости от обстоятельств вы можете выполнить частично перекрывающуюся последнюю итерацию, которая доходит точно до конца массива, но также частично перезаписывает некоторые данные, которые были записаны последней «нормальной» итерацией. Или вам, возможно, просто придется обработать последние элементы скалярным кодом.
  • В многопоточном сценарии «смешанные хранилища» небезопасны для одновременного изменения. Данные могут быть потеряны. У меня нет никаких предложений по этому поводу, кроме как избежать попадания в этот сценарий.

В особом случае добавления 12 к некоторому подмножеству слов вы можете добавить 12 к элементам, которые вы хотите изменить, и 0 к элементам, которые вы не хотите менять, с учетом тех же двух проблем. Довольно часто можно сделать что-то подобное вместо полного смешивания со старыми данными, но, конечно, иногда вам просто нужно смешать.

Меня также интересует влияние такой операции на производительность на ushorts со смещением и маской, которая, как я полагаю, будет очень дорогостоящей из-за невыравнивания?

Все процессоры с поддержкой AVX2 достаточно хорошо справляются с (нормальной) невыровненной загрузкой и сохранением. Обычно это не имеет большого значения, как это было на процессорах эпохи Core2. Разделенные блокировки по-прежнему плохи, но вы выполняете операции SIMD, а не операции RMW с lock.

Я не уверен, что maskmovdqu когда-либо будет хорошо работать на каких-либо многоядерных процессорах. Он (или версия MMX) мог бы быть хорош на старых одноядерных процессорах, у которых было меньше проблем с когерентностью кэша, и, возможно, мог бы напрямую транслировать замаскированное хранилище в команду контроллера DRAM, которая использует побайтовые строки включения для Burst, которые раньше были обязательными, но являются необязательными, по крайней мере, в DDR4. (Однако одноядерные процессоры не имели встроенных контроллеров памяти, за исключением некоторых ранних моделей AMD, поэтому хранилище все равно должно было идти через переднюю шину к северному мосту.)

Peter Cordes 13.07.2024 18:01

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

Peter Cordes 13.07.2024 18:02

Я попробовал использовать BlendVariable, и это решило мою проблему, однако теперь у меня возникла еще одна проблема, о которой я не подумал. В моем сценарии маска смешивания должна быть динамической. то есть мне нужно что-то вроде var blendMask = Vector256<ushort>.Create(numOfElementsToInclude) ... есть ли что-нибудь подобное, о чем вы могли бы знать? или, возможно, возможность сдвинуться и оставить всю маску? таким образом я мог бы начать со всех FFFF и т. д., а затем сдвинуть их на количество мест, чтобы создать динамическую маску?

creativergk 13.07.2024 18:10

@creativergk вы можете превратить несколько элементов в соответствующую маску, выполнив невыровненную загрузку, используя 2*(16 - numberOfElements) в качестве смещения в память, в которой хранится 0xffff, 0xffff, ...., 0, 0

user555045 13.07.2024 18:14

ой! умный. Буду ли я использовать динамическую память или есть способ сделать это в стеке для повышения производительности?

creativergk 13.07.2024 18:19

@creativergk, что бы вы ни захотели, вы можете сделать это в стеке, например, с помощью массива stackalloc, но я думаю, вы можете сделать это, не выделяя какой-либо массив. Я забыл подробности, но IIRC, получающий диапазон только для чтения для константы массива, по сути, компилируется в получение указателя на массив в сегменте данных, без какого-либо выделения.

user555045 13.07.2024 18:22

@creativergk может понравиться это, я не знаю, но это показывает концепцию. Никакого распределения.

user555045 13.07.2024 18:30

Да, я именно так и пробовал, работает отлично. Я проведу несколько тестов, чтобы посмотреть, как это повлияет на производительность.

creativergk 13.07.2024 18:32

кажется, работает немного лучше с public static readonly ushort[] = ...

creativergk 13.07.2024 18:40

@creativergk, вы также можете попробовать передать счетчик и сравнить с ним вектор 0, 1, 2, 3, 4 ..., в месте, чувствительном к задержке, это, вероятно, лучше

user555045 13.07.2024 19:35

Другие вопросы по теме