Реализуйте triu_indices numpy с avx в C++

Я хотел бы реализовать numpy.triu_indices (a, 1) (обратите внимание, что второй аргумент равен 1) в С ++ с встроенными функциями avx. Приведенный ниже фрагмент кода - невекторизованная версия кода, который я придумал. Здесь a - длина (int), а first и second - два выходных массива.

void triu_indices(int a, int* first, int* second)
{
    int index = 0;
    for (int i=0; i < a; i++)
    {

        for(int j = i+1; j < a; j++)
        {
            first[index] = i;
            second[index] = j;
            index++;
        }

    }
}

В качестве примера вывода, если я даю = 4, то

first = [0,0,0,1,1,2]
second = [1,2,3,2,3,3]

Теперь я хотел бы полностью реализовать это в AVX2 (то есть в векторизованном виде). В конечном итоге функция будет запускаться по всему массиву целых чисел, который предоставит функции переменную a, а выходные массивы first и second будут сохранены в двух родительских массивах.

Не могли бы вы дать мне несколько полезных советов (или фрагмент кода) о том, как векторизовать эту функцию с помощью явных инстринсиков AVX2 (то есть вне зависимости от автоматической векторизации компилятора)? Извините, если это вопрос новичков, так как я недавно начал изучать AVX.

Вы изучали код numpy?

hpaulj 25.05.2018 08:03

Вы действительно хотите создать массив индексов вместо того, чтобы генерировать их на лету, когда это необходимо? Вы можете сгенерировать пару i, j по линейному индексу, используя Как преобразовать индексы треугольной матрицы в координаты строки, столбца? или алгоритм для порядковых номеров коэффициентов треугольной матрицы, и, что более важно, вы можете петля над треугольной матрицей без каких-либо дорогостоящих операций. Относительно i, j к линейному, см. алгоритм адресации памяти треугольных матриц.

Peter Cordes 25.05.2018 08:08

AVX преимущественно работает с плавающей запятой, поэтому вряд ли это поможет. Однако вы можете использовать AVX2, который поддерживает целочисленные значения, если это есть у вашего процессора. В противном случае придерживайтесь SSE.

Paul R 25.05.2018 08:10

@hpaulj Да, очевидно, он вызывает numpy.tri (), который сам вызывает numpy.greater_equal.outer (). На данный момент я застрял, так как не смог найти ссылку на то, как он это делает.

Roy_123 25.05.2018 08:14

При автоматической векторизации с треугольными матрицами вы можете дополнить индексирование, чтобы каждая строка начиналась с 32-байтовой выровненной границы. то есть округлить длину каждой строки до числа, кратного 8 элементам float, целому вектору AVX. Это также упрощает цикл по матрице с векторами AVX: вы можете хранить мусор в заполнении в конце строки вместо того, чтобы последний вектор строки включал некоторые элементы с начала следующей строки.

Peter Cordes 25.05.2018 08:15

@PeterCordes Мне действительно нужен массив индексов. Но я посмотрю на ваши ссылки

Roy_123 25.05.2018 08:16

@PaulR да, я могу использовать avx2. Я тоже хотел поставить этот тег, но, к сожалению, SO допускает только 5 тегов

Roy_123 25.05.2018 08:17

Без смещений функция numpy просто: np.where(np.arange(N)>=np.arange(N)[:,None]). np.tri генерирует логическую маску, которая истинна в верхнем треугольнике, а np.where находит координаты истинных значений. Это просто объединение пары строительных блоков целого массива.

hpaulj 25.05.2018 08:36

@hpaulj Спасибо. Это отличная подсказка. Но разве это не будет более неэффективным, чем текущая версия с двумя контурами?

Roy_123 25.05.2018 08:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
179
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Прежде всего, убедитесь, что вам В самом деле нужно это сделать, и вам действительно нужны массивы индексов как конечный результат, а не как часть отслеживания данных в треугольной матрице. AVX2 имеет поддержку сборки, а AVX512 имеет поддержку разброса, но введение массива индексов значительно ухудшает SIMD.

Для перехода по треугольным матрицам и для i, j к линейному, см. алгоритм адресации памяти треугольных матриц с помощью сборки. (Возможно, вы захотите дополнить индексирование, чтобы каждая строка начиналась с 32-байтовой выровненной границы. То есть округлите длину каждой строки до числа, кратного 8 элементам с плавающей запятой, всего вектора AVX. Это также упрощает цикл по матрица с векторами AVX: вы можете хранить мусор в заполнении в конце строки вместо того, чтобы последний вектор строки включал некоторые элементы с начала следующей строки.)

Для linear -> i, j формула в закрытой форме включает sqrt (также Версия C++), поэтому вполне возможно, что поиск в массиве может быть полезен, но на самом деле вам следует избегать этого вообще. (например, если перебирать треугольную матрицу в упакованном формате, отслеживайте, где вы находитесь, в i, j, а также в линейном, чтобы вам не понадобился поиск, когда вы найдете элемент, который ищете.)


Если вам это нужно:

Для больших массивов он довольно хорошо разбивается на целые векторы, усложняется только на концах строк.

Вы можете использовать предопределенную векторную константу для особого случая последнего угла, когда у вас есть несколько строк треугольников в одном и том же векторе из 4 или 8 элементов int.

first = [0,0,0,1,1,2]

В случае большего треугольника мы сохраняем большие серии с одним и тем же номером (например, memset), затем несколько более короткую серию следующего номера и т. д., То есть хранить целый ряд 0 легко. Для всех строк, кроме последней пары, эти прогоны превышают 1 элемент вектора.

second = [1,2,3,2,3,3]

Опять же, в пределах одной строки это простой шаблон для векторизации. Чтобы сохранить возрастающую последовательность, начните с вектора {1,2,3,4} и увеличивайте его с помощью SIMD, добавьте с помощью {4,4,4,4}, то есть _mm_set1_epi32(1). Для 256-битных векторов AVX2 _mm256_set1_epi32(8) увеличивает 8-элементный вектор на 8.

Итак, в самом внутреннем цикле вы просто сохраняете один инвариантный вектор и используете _mm256_add_epi32 для другого и сохраняете его в другом массиве.

Компиляторы уже могут авто-векторизовать вашу функцию довольно прилично, хотя обработка конца строки намного сложнее, чем вы могли бы сделать вручную. С вашим кодом в проводнике компилятора Godbolt__restrict, чтобы сообщить компилятору, что выходные массивы не перекрываются, и __builtin_assume_aligned, чтобы сообщить компиляторам, что они выровнены), мы получаем такой внутренний цикл (из gcc):

.L4:                                       # do {
    movups  XMMWORD PTR [rcx+rax], xmm0      # _mm_store_si128(&second[index], xmm0)
    paddd   xmm0, xmm2                       # _mm_add_epi32
    movups  XMMWORD PTR [r10+rax], xmm1      # _mm_store_si128(&second[index], counter_vec)
    add     rax, 16                          # index += 4  (16 bytes)
    cmp     rax, r9
    jne     .L4                            # }while(index != end_row)

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

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

vsecond = _mm256_add_epi32(vsecond, _mm256_set1_epi32(1));
vfirst  = _mm256_add_epi32(vfirst, _mm256_set1_epi32(1));

т.е. превратить {0,0,0,0,...} в {1,1,1,1,...}, добавив вектор всех единиц. И превратите {1,2,3,4,5,6,7,8} в {2,3,4,5,6,7,8,9}, добавив вектор всех единиц.

Большое спасибо! К сожалению, мне нужно хранить индексы. Я немного отредактирую вопрос, чтобы объяснить, что я собираюсь с этим делать. Буду с нетерпением ждать вашего более подробного объяснения.

Roy_123 25.05.2018 09:46

@ Roy_123: Я знаю, что они нужны ты; Вы сказали об этом в комментариях. Первая часть этого SO-ответа предназначена для будущих читателей, которые попадают сюда, когда ищут, как делать SIMD с треугольными матрицами. Вторая половина моего ответа уже показывает вам, как эффективно векторизовать большую часть работы.

Peter Cordes 25.05.2018 09:53

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