Векторы в си шарп

Небольшой обзор SIMD в .NET/C#

Вашему вниманию предлагается небольшой обзор возможностей векторизации алгоритмов в .NET Framework и .NETCORE. Цель статьи познакомить с этими приёмами тех, кто их вообще не знал и показать, что .NET не сильно отстаёт от «настоящих, компилируемых» языков для нативной
разработки.

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

Видео:vector | Библиотека стандартных шаблонов (stl) | Уроки | C++ | #1Скачать

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. ВекторСкачать

Программирование на С++. Урок 70. Вектор

Суммируем элементы массива

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

С использованием LINQ

С использованием векторов из System.Numerics:

С использованием кода из пространства System.Runtime.Intrinsics:

Я запустил бенчмарк на эти 4 метода у себя на компьютере и получил такой результат:

MethodItemsCountMedian
Naive1075.12 ns
LINQ101 186.85 ns
Vectors1060.09 ns
Intrinsics10255.40 ns
Naive100360.56 ns
LINQ1002 719.24 ns
Vectors10060.09 ns
Intrinsics100345.54 ns
Naive10001 847.88 ns
LINQ100012 033.78 ns
Vectors1000240.38 ns
Intrinsics1000630.98 ns
Naive1000018 403.72 ns
LINQ10000102 489.96 ns
Vectors100007 316.42 ns
Intrinsics100003 365.25 ns
Naive100000176 630.67 ns
LINQ100000975 998.24 ns
Vectors10000078 828.03 ns
Intrinsics10000041 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 | Структуры данныхСкачать

#7. Реализация динамического массива на С++ с помощью std::vector | Структуры данных

Сравниваем два массива

Надо сравнить два массива байт. Собственно это та задача, из-за которой я начал изучать SIMD в .NET. Напишем опять несколько методов для бенчмарка, будем сравнивать два массива: ArrayA и ArrayB:

Самое очевидное решение:

Решение через LINQ:

Решение через функцию MemCmp:

С использованием векторов из System.Numerics:

С использованием Intrinsics:

Результат бэнчмарка на моём компьютере:

MethodItemsCountMedian
Naive1000066 719.1 ns
LINQ1000071 211.1 ns
Vectors100003 695.8 ns
MemCmp10000600.9 ns
Intrinsics100001 607.5 ns
Naive100000588 633.7 ns
LINQ100000651 191.3 ns
Vectors10000034 659.1 ns
MemCmp1000005 513.6 ns
Intrinsics10000012 078.9 ns
Naive10000005 637 293.1 ns
LINQ10000006 622 666.0 ns
Vectors1000000777 974.2 ns
MemCmp1000000361 704.5 ns
Intrinsics1000000434 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 и vector в языке программирования с++

Подсчитываем сколько раз элемент встречается в коллекции

Иногда требуется посчитать сколько раз конкретный элемент встречается в коллекции, например, интов, этот алгоритм тоже можно ускорить. Напишем несколько методов для сравнения, будем искать в массиве Array элемент Item.

с использованием LINQ:

с использованием векторов из System.Numerics.Vectors:

С использованием Intrinsics:

Результат бенчмарка на моём компьютере:

MethodItemsCountMedian
Naive10002 824.41 ns
LINQ100012 138.95 ns
Vectors1000961.50 ns
Intrinsics1000691.08 ns
Naive1000027 072.25 ns
LINQ10000113 967.87 ns
Vectors100007 571.82 ns
Intrinsics100004 296.71 ns
Naive100000361 028.46 ns
LINQ1000001 091 994.28 ns
Vectors10000082 839.29 ns
Intrinsics10000040 307.91 ns
Naive10000001 634 175.46 ns
LINQ10000006 194 257.38 ns
Vectors1000000583 901.29 ns
Intrinsics1000000413 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Скачать

Базовый курс С++ Часть #81. Вектор std::vector

BestProg

Видео:[C++] STL: VectorСкачать

[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 урокиСкачать

Векторы и Манипуляции с ними, Vector3 - Unity уроки

Как создать вектор в С#

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

Вероятно, вы хотите использовать общий List класс, как в List .

Скажем, у вас есть имена ваших серверов в строке значений, разделенных запятыми. Затем вы можете использовать метод расширения Split и ToList() (в IEnumerable), чтобы преобразовать его в список с динамическим размером.

Или, если вы проверяете их по одному с помощью другого метода.

🎥 Видео

ЯЗЫК C++ #19 — ВЕКТОРСкачать

ЯЗЫК C++ #19 — ВЕКТОР

Готовимся к ЕГЭ по физике на 80+ | Саня ЭбонитСкачать

Готовимся к ЕГЭ по физике на 80+ | Саня Эбонит

Собеседование C# Junior developer, что спрашивают в 2021 году?! Техподдержка идет программировать.Скачать

Собеседование C# Junior developer, что спрашивают в 2021 году?! Техподдержка идет программировать.

C# 2023 С НУЛЯ ДО ПРОФИ | СЛИВ ЛУЧШЕГО КУРСАСкачать

C# 2023 С НУЛЯ ДО ПРОФИ | СЛИВ ЛУЧШЕГО КУРСА

Программирование на С++. Урок 71. Пример работы с вектором. Двумерный вектор.Скачать

Программирование на С++. Урок 71. Пример работы с вектором. Двумерный вектор.

С++. Урок 14. Вектор из STLСкачать

С++. Урок 14. Вектор из STL

04.1 Массивы и векторы в C++ на примерах задачСкачать

04.1 Массивы и векторы в C++ на примерах задач

C++ 22. Внутреннее устройство vectorСкачать

C++ 22. Внутреннее устройство vector

26. Умные указатели: unique_ptr, shared_ptrСкачать

26. Умные указатели: unique_ptr, shared_ptr
Поделиться или сохранить к себе: