Мотивация
Прежде всего, что на самом деле означает SIMD? Я определенно не слышал об этом до тех пор, пока не изучал информатику несколько лет назад. Это сокращение от Одна инструкция, несколько данных (SIMD) и связано с архитектурой ЦП и ГП. Другой акроним, часто встречающийся рядом с SIMD, - Streaming SIMD Extensions (SSE). Вы можете прочитать теоретические подробности об этом в различных публикациях и по всему Интернету, но я постараюсь сформулировать его упрощенно и практично: в основном для вас, как кодировщика, SIMD позволяет выполнять четыре операции (чтение / запись / вычисление ) по цене одной инструкции. Снижение затрат достигается за счет векторизации и параллелизма данных. Какая сделка! И вам даже не нужно обрабатывать потоки и условия гонки, чтобы добиться этого параллелизма. Тебе лучше воспользоваться этим.

Проблема
Итак, с концептуальной точки зрения, как мы можем воплотить это в код? Предположим, у нас есть массив позиций (три числа с плавающей запятой в векторе), и мы хотим вычислить среднее значение всех позиций. Для этого нам просто нужно перебрать массив, просуммировать все позиции и разделить результат на количество элементов в массиве. Если говорить о C # в Unity3D, то «нормальный» способ его реализации может выглядеть примерно так:

public Vector3 Average(Vector3[] positions)
{
   Vector3 summed = Vector3.zero;
 
   for(int i=0;i<positions.Length;i++)
   {
      summed += positions[i];
   }
   return summed / positions.Length;
}

К сожалению, SIMD напрямую не поддерживается в Unity. Во-первых, я подумал, что вы можете использовать структуру Vector4 вместо Vector3, предоставленную UnityEngine, но это тоже не работает. Таким образом, вы должны найти собственное решение. На мой взгляд, у вас есть следующие возможности:

1. Обычно преимущества SIMD используются в C ++ и связанных математических библиотеках. Следовательно, вы можете закодировать часть алгоритма на C ++. Затем вам нужно будет перенести данные из Unity в DLL, вычислить результат «извне» в коде C ++ и передать данные обратно. Это может быть жизнеспособным вариантом, если требуется много вычислений, но возникает проблема передачи данных туда и обратно. Кроме того, я думаю, что это может быть более подверженным ошибкам из-за передачи данных и может замедлить процесс разработки, поскольку необходимо проверить правильность работы и связи между двумя отдельными компонентами.

2. Вы можете интегрировать плагины в Unity и реализовывать операции SIMD прямо на C #. Поскольку этот вариант проще и должен работать с небольшими выборками, я остановлюсь на нем подробнее.

Решение на C # с .NET 2.0
Хотя Unity не поддерживает (пока?) операции SIMD, вы можете воспользоваться преимуществом Mono.simd.dll, скрытого в папке установки Unity. В зависимости от версии, которую вы предпочитаете, просто скопируйте .dll из PathToUnity\Editor\Data\Mono\lib\mono\2.0over в свой проект. Он предоставляет 16-байтовые структуры данных, такие как вектор с четырьмя числами с плавающей запятой или целые числа. Используя using Mono.simd;, мы можем изменить предыдущий код следующим образом:

public Vector3 AverageSIMD(Vector3[] positions)
{
   Vector4f summed = new Vector4f(0, 0, 0, 0);
   
   for (int i = 0; i < positions.Length; i++)
   {
      summed += new Vector4f(positions[i].x,
                             positions[i].y,
                             positions[i].z, 1.0f);
   }
   
   summed /= new Vector4f(positions.Length, 
                          positions.Length, 
                          positions.Length, 1.0f);
   return new Vector3(summed.X, summed.Y, summed.Z);
 }

На каждом шаге цикла стоимость суммирования трех значений с плавающей запятой была уменьшена до одной инструкции. Для одного шага в цикле этого может показаться немного. Однако, в зависимости от количества позиций, которые мы должны суммировать, прирост производительности будет значительным. Предоставленная версия может быть дополнительно оптимизирована, если массив позиций снабжен Vector4f непосредственно при вызове метода, так что циклу не нужно преобразовывать каждый Vector3 в Vector4f. Сравнение каждого варианта со случайно сгенерированными позициями дало следующие результаты:

В то время как SIMD с Vector3 преобразованием занимает около 66% от исходной продолжительности, SIMD без преобразования на самом деле занимает около 22% от исходной продолжительности. Аккуратно, не правда ли?

Заключение
SIMD предлагает мощные способы повышения производительности и может быть реализован в Unity. В этой статье показано, как применить его к простой проблеме, но я надеюсь, что вы захотите применить его и к другим задачам. Как можно применить эти знания для вычисления различных степеней числа, скалярного произведения или полинома? (Возможно, я продемонстрирую это в другой статье). Подумайте, как разумно упаковать значения с плавающей запятой в предоставленные векторные структуры и работать с ними. Профилируйте его и посмотрите, стоило ли изменение того.
На будущее было бы здорово узнать, как производительность сравнивается с альтернативой C ++, упомянутой ранее в этой статье. Более того, более новые версии Unity (2017+) уже предоставляют экспериментальную поддержку .NET 4.6, которая предлагает System.Numerics. Быстрый тест показал, что эти векторные структуры превосходят аналог Mono.Simd. Кроме того, UnityEngine.Vector3, похоже, быстрее выполняет вычисления с .NET 4.6, но мне нужно изучить это подробнее. Когда Unity обеспечит стабильную поддержку .NET 4.6, новые структуры определенно станут очень жизнеспособными вариантами для улучшений SIMD.

[ОБНОВЛЕНИЕ 03/2018] Теперь, когда компилятор C # Entity-Component-System, Job System и Burst скоро будет доступен, вам следует подумать об их использовании: Unite Austin 2017 - Написание высокопроизводительных сценариев C #

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

Всего наилучшего, Броман