Вашему вниманию предлагается небольшой обзор возможностей векторизации алгоритмов в .NET Framework и .NETCORE. Цель статьи познакомить с этими приёмами тех, кто их вообще не знал и показать, что .NET не сильно отстаёт от «настоящих, компилируемых» языков для нативной
разработки.
Я только начинаю изучать приёмы векторизации, так что если кто из сообщества укажет мне на явные косяк, или предложит улучшенные версии описанных ниже алгоритмов, то буду дико рад.
- Немного истории
- Суммируем элементы массива
- Сравниваем два массива
- Подсчитываем сколько раз элемент встречается в коллекции
- Заключение
- BestProg
- Класс vector . Динамический массив. Общие сведения. Обзор методов. Создание динамического массива. Способы доступа к элементам вектора
- Содержание
- Как создать вектор в С#
- 🎥 Видео
Видео:vector | Библиотека стандартных шаблонов (stl) | Уроки | C++ | #1Скачать
Немного истории
В .NET SIMD впервые появился в 2015 году с выходом .NET Framework 4.6. Тогда были добавлены типы Matrix3x2, Matrix4x4, Plane, Quaternion, Vector2, Vector3 и Vector4, которые позволили прозводить векторизированные вычисления. Позже был добавлен тип Vector , который предоставил больше возможностей для векторизации алгоритмов. Но многие программисты всё равно были недовольны, т.к. вышеописанные типы ограничивали поток мыслей программиста и не давали возможности использовать полную мощь SIMD-инструкций современных процессоров. Уже в наше время, в .NET Core 3.0 Preview появилось пространство имён System.Runtime.Intrinsics, которое предоставляет гораздо большую свободу в выборе инструкций. Чтобы получить наилучшие результаты в скорости надо использовать RyuJit и нужно либо собирать под x64, либо отключить Prefer 32-bit и собирать под AnyCPU. Все бенчмарки я запускал на компьютере с процессором Intel Core i7-6700 CPU 3.40GHz (Skylake).
Видео:Программирование на С++. Урок 70. ВекторСкачать
Суммируем элементы массива
Я решил начать с классической задачи, которую часто пишут первой, когда речь заходит про векторизацию. Это задача нахождения суммы элементов массива. Напишем четыре реализации этой задачи, будем суммировать элементы массива Array:
С использованием LINQ
С использованием векторов из System.Numerics:
С использованием кода из пространства System.Runtime.Intrinsics:
Я запустил бенчмарк на эти 4 метода у себя на компьютере и получил такой результат:
Method | ItemsCount | Median |
---|---|---|
Naive | 10 | 75.12 ns |
LINQ | 10 | 1 186.85 ns |
Vectors | 10 | 60.09 ns |
Intrinsics | 10 | 255.40 ns |
Naive | 100 | 360.56 ns |
LINQ | 100 | 2 719.24 ns |
Vectors | 100 | 60.09 ns |
Intrinsics | 100 | 345.54 ns |
Naive | 1000 | 1 847.88 ns |
LINQ | 1000 | 12 033.78 ns |
Vectors | 1000 | 240.38 ns |
Intrinsics | 1000 | 630.98 ns |
Naive | 10000 | 18 403.72 ns |
LINQ | 10000 | 102 489.96 ns |
Vectors | 10000 | 7 316.42 ns |
Intrinsics | 10000 | 3 365.25 ns |
Naive | 100000 | 176 630.67 ns |
LINQ | 100000 | 975 998.24 ns |
Vectors | 100000 | 78 828.03 ns |
Intrinsics | 100000 | 41 269.41 ns |
Видно, что решения с Vectors и Intrinsics очень сильно выигрывают в скорости по сравнению с очевидным решением и с LINQ. Теперь надо разобраться что происходит в этих двух методах.
Рассмотрим подробнее метод Vectors:
Если взглянуть в код метода Intrinsics:
То можно увидеть, что он очень похож на Vectors за некоторым исключением:
- vectorSize задан константой. Так происходит потому что в этом методе явно используются Avx2 инструкции, которые оперируют с 256 битными регистрами. В реальном приложении должна быть проверка на то поддерживает ли текущий процессор Avx2 инструкции и если не поддерживает, вызывать другой код. Выглядит это примерно так:
- var accVector = Vector256 .Zero; accVector объявлен как 256 битный вектор, заполненный нулями.
- fixed (int* ptr = Array) — в ptr заносится указатель на array.
- Дальше такие же операции как и в Vectors: загрузка данных в вектор и сложение двух векторов.
- Для суммирования элементов вектора был применён такой способ:
- создаётся массив на стэке: var temp = stackalloc int[vectorSize];
- загружается вектор в этот массив: Avx2.Store(temp, accVector);
- в цикле суммируются элементы массива.
- потом досуммируются элементы массива, которые не помещаются в последний вектор
Видео:#7. Реализация динамического массива на С++ с помощью std::vector | Структуры данныхСкачать
Сравниваем два массива
Надо сравнить два массива байт. Собственно это та задача, из-за которой я начал изучать SIMD в .NET. Напишем опять несколько методов для бенчмарка, будем сравнивать два массива: ArrayA и ArrayB:
Самое очевидное решение:
Решение через LINQ:
Решение через функцию MemCmp:
С использованием векторов из System.Numerics:
С использованием Intrinsics:
Результат бэнчмарка на моём компьютере:
Method | ItemsCount | Median |
---|---|---|
Naive | 10000 | 66 719.1 ns |
LINQ | 10000 | 71 211.1 ns |
Vectors | 10000 | 3 695.8 ns |
MemCmp | 10000 | 600.9 ns |
Intrinsics | 10000 | 1 607.5 ns |
Naive | 100000 | 588 633.7 ns |
LINQ | 100000 | 651 191.3 ns |
Vectors | 100000 | 34 659.1 ns |
MemCmp | 100000 | 5 513.6 ns |
Intrinsics | 100000 | 12 078.9 ns |
Naive | 1000000 | 5 637 293.1 ns |
LINQ | 1000000 | 6 622 666.0 ns |
Vectors | 1000000 | 777 974.2 ns |
MemCmp | 1000000 | 361 704.5 ns |
Intrinsics | 1000000 | 434 252.7 ns |
Весь код этих методов, думаю, понятен, за исключением двух строк в Intrinsics:
В первой два вектора сравниваются на равенство и результат сохраняется в вектор areEqual, у которого в элемент на конкретной позиции все биты выставляются в 1, если соответствующие элементы в va и vb равны. Получается, что если векторы из байт va и vb полностью равны, то в areEquals все элементы должны равняться 255 (11111111b). Т.к. Avx2.CompareEqual является обёрткой над _mm256_cmpeq_epi8, то на сайте Intel можно посмотреть псевдокод этой операции:
Метод MoveMask из вектора делает 32-х битное число. Значениями битов являются старшие биты каждого из 32 однобайтовых элементов вектора. Псевдокод можно посмотреть здесь.
Таким образом, если какие-то байты в va и vb не совпадают, то в areEqual соответствующие байты будут равны 0, следовательно старшие биты этих байт тоже будут равны 0, а значит и соответствующие биты в ответе Avx2.MoveMask тоже будут равны 0 и сравнение с equalsMask не пройдёт.
Разберём небольшой пример, приняв что длина вектора 8 байт (чтобы писать было меньше):
- Пускай va = , а vb = ;
- Тогда areEqual будет равен ;
- Метод MoveMask вернёт 10011100b, который нужно будет сравнивать с маской 11111111b, т.к. эти маски неравны, то выходит, что и векторы va и vb неравны.
Видео:array и vector в языке программирования с++Скачать
Подсчитываем сколько раз элемент встречается в коллекции
Иногда требуется посчитать сколько раз конкретный элемент встречается в коллекции, например, интов, этот алгоритм тоже можно ускорить. Напишем несколько методов для сравнения, будем искать в массиве Array элемент Item.
с использованием LINQ:
с использованием векторов из System.Numerics.Vectors:
С использованием Intrinsics:
Результат бенчмарка на моём компьютере:
Method | ItemsCount | Median |
---|---|---|
Naive | 1000 | 2 824.41 ns |
LINQ | 1000 | 12 138.95 ns |
Vectors | 1000 | 961.50 ns |
Intrinsics | 1000 | 691.08 ns |
Naive | 10000 | 27 072.25 ns |
LINQ | 10000 | 113 967.87 ns |
Vectors | 10000 | 7 571.82 ns |
Intrinsics | 10000 | 4 296.71 ns |
Naive | 100000 | 361 028.46 ns |
LINQ | 100000 | 1 091 994.28 ns |
Vectors | 100000 | 82 839.29 ns |
Intrinsics | 100000 | 40 307.91 ns |
Naive | 1000000 | 1 634 175.46 ns |
LINQ | 1000000 | 6 194 257.38 ns |
Vectors | 1000000 | 583 901.29 ns |
Intrinsics | 1000000 | 413 520.38 ns |
Методы Vectors и Intrinsics полностью совпадают в логике, отличия только в реализации конкретных операций. Идея в целом такая:
- создаётся вектор mask, в котором в каждом элементе храниться искомое число;
- Загружается в вектор v часть массива и сравнивается с маской, тогда в равных элементах в areEqual будут выставлены все биты, т.к. areEqual — вектор из интов, то, если выставить все биты одного элемента, получим -1 в этом элементе ((int)(1111_1111_1111_1111_1111_1111_1111_1111b) == -1);
- вычитается из accVector вектор areEqual и тогда в accVector будет сумма того, сколько раз элемент item встретился во всех векторах v для каждой позиции (минус на минут дают плюс).
Весь код из статьи можно найти на GitHub
Видео:векторы С++Скачать
Заключение
Я рассмотрел лишь очень малую часть возможностей, которые предоставляет .NET для векторизации вычислений. За полным и актуальным список доступных интринсиков в .NETCORE под x86 можно обратиться к исходному коду. Удобно, что там в C# файлах в summary каждого интринсика есть его же название из мира C, что упрощает и понимание назначения этого интринсика, и перевод уже существующих С++/С алгоритмов на .NET. Документация по System.Numerics.Vector доступна на msdn.
На мой вгляд, у .NET есть большое преимущество перед C++, т.к. JIT компиляция происходит уже на клиентской машине, то компилятор может оптимизировать код под конкретный клиентский процессор, предоставляя максимальную производительность. При этом программист для написания быстрого кода может оставаться в рамках одного языка и технологий.
В бенчмарках я использовал IterationSetup, которые, как выяснилось, может сильно влиять на показатели бенчмарков, которые отрабатывают меньше чем за 100мс. Если переделать на GlobalSetup, то результаты будут вот такие.
Видео:Базовый курс С++ Часть #81. Вектор std::vectorСкачать
BestProg
Видео:[C++] STL: VectorСкачать
Класс vector . Динамический массив. Общие сведения. Обзор методов. Создание динамического массива. Способы доступа к элементам вектора
Данная тема является началом изучения средств класса vector , который представляет собой динамический массив.
Содержание
Поиск на других ресурсах:
1. Общие сведения о классе vector . Обзор методов класса
Класс vector представляет собой динамический массив, размер которого в программе может изменяться при необходимости. Этот класс является одним из самых универсальных и распространенных в использовании при написании программ на C++. Доступ к элементам класса осуществляется как к обычному массиву с помощью квадратных скобок [ ] .
Чтобы использовать методы и константы массива типа vector нужно подключить заголовок и пространство имен std
Шаблонная спецификация класса vector имеет следующий вид:
- T – тип элементов массива. Это может быть любой базовый тип ( int , float , char и т.д.) или тип, разработанный пользователем;
- Allocator — название класса, обеспечивающего распределение памяти.
Методы класса vector можно условно разбить на 3 группы:
- 1. Методы, определяющие и изменяющие общие характеристики массива.
- 1.1. Метод size() . Определить размер вектора
- 1.2. Метод max_size() . Максимально допустимый размер массива
- 1.3. Метод capacity() . Определить размер массива с учетом зарезервированной памяти
- 1.4. Метод empty() . Определить, пустой ли вектор
- 1.5. Метод shrink_to_fit() . Выровнять размер массива с учетом зарезервированной памяти по фактическому размеру массива
- 1.6. Метод reserve() . Зарезервировать память для дополнительных элементов массива
- 1.7. Метод resize() . Изменить размер массива
- 2. Методы, модифицирующие (изменяющие) данные в массиве
- 2.1. Метод push_back() . Добавить элемент в конец вектора
- 2.2. Метод pop_back() . Удалить последний элемент вектора
- 2.3. Метод clear() . Удаляет из массива все элементы
- 2.4. Метод swap() . Обмен местами двух векторов
- 2.5. Присваивание массивов. Перегруженный оператор =
- 2.6. Метод erase() . Удалить один или несколько элементов заданного диапазона
- 2.7. Метод insert() . Вставляет элемент или группу элементов в вектор
- 2.7.1. Вставка списка инициализации в вектор
- 2.7.2. Вставка элемента заданное количество раз в заданную позицию
- 2.7.3. Вставка одиночного элемента в заданную позицию
- 2.7.4. Вставка нескольких элементов из заданного диапазона
- 2.8. Метод assign() . Создать массив из существующих данных
- 3. Методы, обеспечивающие доступ к элементам массива
- 3.1 Метод at() . Получить элемент вектора по его позиции (индексу)
- 3.2. Метод front() . Возвращает ссылку на первый элемент вектора
- 3.3. Метод back() . Возвращает ссылку на последний элемент вектора
- 3.4. Метод data() . Получить указатель на вектор
- 3.5. Метод begin() . Вернуть итератор, указывающий на первый элемент вектора
- 3.6. Метод end() . Вернуть итератор, указывающий на последний элемент массива
- 3.7. Методы cbegin() , cend() . Получить константный итератор на начало и конец массива
- 3.8. Методы rbegin() , rend() . Доступ к элементам массива с помощью реверсного итератора
- 3.9. Методы crbegin() , crend() . Установить на начало и конец массива константный реверсный итератор
2. Создание динамического массива типа vector . Конструкторы. Пример
Для создания массива в классе vector объявляется ряд конструкторов. Ниже приведены наиболее распространенные из них.
Чтобы создать пустой массив используется конструктор:
Чтобы создать массив, который содержит num элементов со значением val , используется конструктор
Чтобы создать массив на основе другого массива ob , нужно использовать конструктор
Чтобы создать массив на основе диапазона на который указывают итераторы start и end , используется следующий конструктор
Пример. В примере создаются различные массивы типа vector .
3. Способы доступа к элементам вектора. Индексирование [ ]
После создания вектора, можно получать доступ к его элементам одним из следующих способов:
- с помощью операции индексирования [ ] , как в случае с обычным массивом;
- с помощью итератора;
- с помощью метода at() .
Видео:Векторы и Манипуляции с ними, Vector3 - Unity урокиСкачать
Как создать вектор в С#
У меня есть набор серверов, для которых мне нужны имена, но мне нужны только те, которые в настоящее время доступны, поэтому мне нужен массив с динамическим размером. Какую структуру данных я могу использовать для их хранения.
Вероятно, вы хотите использовать общий List класс, как в List .
Скажем, у вас есть имена ваших серверов в строке значений, разделенных запятыми. Затем вы можете использовать метод расширения Split и ToList() (в IEnumerable), чтобы преобразовать его в список с динамическим размером.
Или, если вы проверяете их по одному с помощью другого метода.
🎥 Видео
ЯЗЫК C++ #19 — ВЕКТОРСкачать
Готовимся к ЕГЭ по физике на 80+ | Саня ЭбонитСкачать
Собеседование C# Junior developer, что спрашивают в 2021 году?! Техподдержка идет программировать.Скачать
C# 2023 С НУЛЯ ДО ПРОФИ | СЛИВ ЛУЧШЕГО КУРСАСкачать
Программирование на С++. Урок 71. Пример работы с вектором. Двумерный вектор.Скачать
С++. Урок 14. Вектор из STLСкачать
04.1 Массивы и векторы в C++ на примерах задачСкачать
C++ 22. Внутреннее устройство vectorСкачать
26. Умные указатели: unique_ptr, shared_ptrСкачать