Генерация случайных чисел векторизованного ранжирования для всех типов - PullRequest
0 голосов
/ 02 декабря 2018

Я хочу поддержать следующую операцию в C ++:

void generate_random_simd(T* array, T upper_bound, T lower_bound) {
 // uses simd instructions for rng in range [lower_bound, upper_bound]
}

Тип T может быть любым типом uint, int или float - 32 или 64 бит.Есть ли эффективная реализация, доступная напрямую или какая-то литература по этому материалу?

Я нашел несколько реализаций, таких как this и this .Но они не поддерживают все вышеперечисленные типы и не поддерживают указание верхней-нижней границы.Их использование, в свою очередь, может потребовать дополнительной обработки для достижения результата, чьи накладные расходы, боюсь, в конечном итоге будут эквивалентны простому циклу и использованию стандартного генератора случайных чисел C ++ (не-simd).

1 Ответ

0 голосов
/ 02 декабря 2018

Границы элементов имеют значение только тогда, когда у вас есть нижняя / верхняя границы.В противном случае для целого числа вы просто хотите 128 или 256 бит случайных данных в векторе SIMD.

Например, вы можете использовать xorshift + SSE2 / AVX2, который запускает несколько генераторов xorshift + в 64-битных элементах SIMD.Вы можете рассматривать это как 16x uint8_t, или 2x uint64_t, или что-то среднее между ними, когда вы хотите использовать случайные данные для чего-либо.

Вот пример использованиячто как 16-битные элементы -> несколько векторов десятичных цифр, в моем ответе на Какой самый быстрый способ создать текстовый файл размером 1 ГБ, содержащий случайные цифры? больше на unix.SE.(Написано на языке C с использованием встроенных функций Intel, с номерами тестов Core 2, Haswell и Skylake.)

Он работает достаточно быстро, поэтому вы захотите использовать вывод, пока он еще горячий в кеше, например, cache-blockкусками по 4 или 8 килобайт для L1d хитов.Или просто используйте вектор случайных чисел по мере их производства.

Вы, конечно, можете использовать другой делитель и добавить что-то к каждому элементу, чтобы получить диапазон, отличный от 0..upper.Но это наиболее эффективно с диапазоном времени компиляции.Тем не менее, вы можете использовать libdivide для SIMD-деления (или по модулю) с помощью переменной времени выполнения.

При неизвестных верхних / нижних границах вы, вероятно, захотите использовать входной вектор только для одного вектора результатов.Когда я оптимизировал максимальную скорость, имело смысл обрабатывать несколько цифр по 0,9 из 16-битного целого, чтобы сохранить работу xorshift +.0..9 - это такая маленькая дробь 0..65535, что осталось много энтропии, и смещение отличается от первого остатка.


FP сложнее, чем целое число, потому что некоторыебитовые комбинации представляют NaN .И вам часто требуется равномерное распределение вдоль линии действительных чисел, а не равномерное распределение конечных битовых комбинаций.(Половина всех представляемых значений float имеет величину меньше 1,0. Чем ближе вы к нулю, тем ближе друг к другу может быть float с.)

По-видимому, обычно генерируют случайные числа FP в[0,1.0) диапазон.(1/4 от общего представимого значения.) Масштабирование до диапазона [0, N) с умножением прекрасно работает для N <2 ^ 24, но для большего значения вы начинаете терять энтропию и вводить смещение, <a href="https://lemire.me/blog/2017/02/28/how-many-floating-point-numbers-are-in-the-interval-01/" rel="nofollow noreferrer"> по ДаниэлюСтатья Лемира «Сколько чисел с плавающей запятой находится в интервале [0,1]?» .

В зависимости от размера вашего диапазона, мне кажется, что их гораздо проще сгенерироватьв диапазоне [1.0, 2.0) (или в любом другом диапазоне одноэкспоненты), путем объединения 23-битного случайного значения (мантиссы) с фиксированным битом экспоненты / знака.

Это меньше битов энтропии, нотривиально равномерно и может быть сделано с SIMD _mm_and_ps и _mm_or_ps.(К сожалению, для этого значение сигнатуры составляет всего 23 бита, а не кратное 8 или 16, поэтому мы не можем просто использовать _mm_blendv_epi8 или _mm_blend_epi16)


Если вы хотите дистрибутивкроме равномерного, (https://en.wikipedia.org/wiki/Random_number_generation#Generation_from_a_probability_distribution), например, Гаусса или Пуассона, вам придется найти алгоритм для этого.


Выборка с отклонением не работает для SIMD из-за требуемойветвление. Возможно, вы могли бы сделать 2 вектора-кандидата случайных чисел и объединить их без ветвления, а затем выполнить ветвление, если что-то еще нужно отклонить.

Может быть, оставив без отклоненных кандидатов левую упаковку, вы сможете довольно эффективно заполнить буферсо случайными числами, производя переменное число на каждой итерации. См. AVX2, каков наиболее эффективный способ упаковки влево на основе маски? для SSE2 / AVX2 / AVX512 левой упаковки.

Сновасохраняйте размер фрагмента буфера достаточно маленьким, чтобы при повторном цикле вы получали L1d или, по крайней мере, попадания в кэш L2.

...