2015-10-13 2 views
17

Я работаю над многопоточной программой, где все потоки разделяют вектор (только для чтения). Цель каждого потока - перемещение всего вектора. Тем не менее, все потоки должны посещать этот вектор по-другому.C++ повторить вектор случайным образом

Поскольку вектор является const и используется для всех потоков, я не могу использовать random_shuffle и просто перебирать его. Сейчас мое решение построить вектор CrossRef, который будет содержать индексы по общему вектору, а затем перетасовать этот вектор, т.е.

 std::vector<int> crossref(SIZE) ; // SIZE is the size of the shared vector 
    std::iota (std::begin(crossref), std::end(crossref), 0); // Fill with indices ref 
    std::mt19937 g(SEED); // each thread has it own seed. 
    std::shuffle (crossref_.begin(), crossref_.end(), g); // Shuffle it 

Тем не менее, делать это выявить некоторые проблемы (1) он не очень эффективен, так как каждый поток должен получить доступ к своему вектору crossref перед доступом к совместно используемому, (2) у меня есть некоторые проблемы с производительностью из-за объема требуемой памяти: общий вектор очень большой, и у меня много потоков и процессоров.

Есть ли у кого-то идеи по улучшению, которые позволят избежать дополнительной памяти?

+0

Доступ к 'std :: vector' выполняется в O (1), так как это произвольный доступ. Также вам не гарантируется, что все потоки будут иметь разные 'crossref'' std :: vector', так что может случиться так, что два потока будут перебирать вектор таким же образом. – Zereges

+0

Я бы использовал один перетасованный индексный стек, общий для всех потоков, который защищен от одновременного доступа. –

+0

@Zereges - Конечно, проблема заключается в том, что общий вектор почти подходит кешу, поэтому каждый раз, когда поток обращается к вектору crossref, он будет аннулировать кеширование, и это не эффективно. – Esus

ответ

14

Вы можете использовать алгебраическое понятие primitive root modulo n. В основном

Если п является положительным целым числом, целые числа от 1 до N - 1, которые взаимно просты с п образуют группу примитивных классов по модулю п. Эта группа циклично тогда и только тогда, когда п равно 2, 4, р^к, или 2р^к, где р^к является степень нечетного простого числа

Википедия показывает, как вы можете создать номера ниже 7 используя 3 как генератор.

enter image description here

Из этого утверждения вы черпаете алгоритм.

  1. Возьмите свой номер n
  2. Найти следующее простое число m, которое больше, чем n
  3. Для каждого из вашего потока выбрать уникальное случайное число F(0) между 2 и m
  4. вычислит следующий индекс, используя F(i+1) = (F(i) * F(0)) mod m. Если этот индекс находится в пределах диапазона [0, n], обратитесь к элементу. Если не перейти к следующему индексу.
  5. Стоп после m - 1 итераций (или когда вы получаете 1, это то же самое).

Поскольку m простое, каждое число от 2 до М-1 взаимно просто с m так является генератором последовательности {1 ... m}. Вам гарантировано, что в первых повторения не будет повторяться число, и все номера m - 1 появятся.

Сложность:

  • Шаг 2: Готово один раз, сложность эквивалентна нахождению простых чисел до п, т.е. sieve of Eratosthenes
  • Шаг 3: Готово один раз, вы можете выбрать 2, 3, 4, 5 , и т. д. ... O(thread count)
  • Шаг 4: O(m) время, O(1) в пространстве на резьбу. Вам не нужно хранить F (i). Вам нужно знать только первое значение и последнее значение. Это те же свойства, что и приращение
+1

Очень элегантное решение! – haavee

+0

Именно то, о чем я думал ... В криптографии эта группа используется для алгоритмов асимметричного шифрования и подписи, где нам также нужна псевдослучайная перестановка, которая должна быть совершенно различной для каждого «ключа» (здесь «F (0) '). – leemes

+0

Спасибо, это действительно изящно! – Esus

2

Если память является вашей самой большой проблемой, вам придется менять циклы процессоров на память.

E.g. C++ std::vector<bool> (http://en.cppreference.com/w/cpp/container/vector_bool) - это бит-массив, который достаточно эффективен для памяти.

Каждая нить может иметь свой собственный vector<bool>, указывающий на то, что она посетила определенный индекс. Затем вам придется использовать циклы CPU, чтобы случайно выбрать индекс, который он еще не посетил, и завершить, когда все bool s - true.

+1

И как искать несортированный вектор bool для «false» n раз, т.е. O (n^2), вместо n доступа к одному массиву, собирается сделать что-нибудь быстрее? – deviantfan

+0

Ну, ОП прямо сказал, что память была его главной проблемой. Вы не можете иметь как эффективность использования пространства, так и эффективность процессора. – haavee

+0

Примечание Вопрос OP ** У кого-нибудь есть идеи по улучшению, которые позволят избежать дополнительной памяти? ** – haavee

6

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

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

Если это так, вы можете сгенерировать перестановку, принимая число р прайм с п и вычислить для каждого я в [1, п]: i.p (mod n). Например, если у вас есть n = 5 и p = 7, то 7% 5 = 2, 14% 5 = 4, 21% 5 = 1, 28% 5 = 3, 35% 5 = 0. Вы можете объединить несколько таких функций, чтобы получить что-то удовлетворяющее для вас ...

+0

Вы имеете в виду, что если каждый поток имеет свой собственный _p_ prime с n, каждый поток может перебирать весь вектор с его перестановкой только путем выполнения 'для каждого i из [1, n]: ip (mod n)' Если это в этом случае я могу легко скомпоновать набор _p_ offline, хорошо ли я понимаю? – Esus

+0

Да, вот и все. И если вы считаете, что такая функция недостаточно очищена, тогда она объединяется со стартовой точкой смещения или вычисляется дважды с двумя разными штрихами. Вы также можете вернуться назад от n до 1 и т. Д. Некоторые разные комбинации могут соответствовать вашим потребностям. –

+0

спасибо! он решает мою проблему, я думаю! – Esus

1

Это не полный ответ, но он должен привести нас к правильному решению.

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

(1) это не очень эффективно, так как каждый поток должен получить доступ к его CrossRef вектора перед обращением к разделяемым один,

Это вряд ли будет правдой. Мы говорим об одном косвенном поиске. Если ваши ссылочные данные действительно являются вектором ints, это будет представлять собой бесконечно малую часть вашего времени выполнения. Если справочные данные вектор Интс, то просто сделать N копий этого и перетасовать их ...

(2) У меня есть некоторые перформансы проблемы из-за объема памяти требуется: общий вектор является очень большой, и у меня много потоков и процессоров.

Насколько велика? Вы его измеряли? Сколько дискретных объектов есть в векторе? Насколько велика каждая из них?

Сколько потоков?

Сколько процессоров?

Сколько у вас памяти?

Профилировали ли вы код? Вы уверены, что, где узкое место производительности? Вы считали более элегантный алгоритм?

+0

Я до 128 потоков с 128 выделенными ядрами. Я работаю с вектором int, который не заполняет всю память, но это справедливое упрощение в отношении моей проблемы. Я использовал профилировщик, и я уверен, что эта часть является узким местом. Я также использую выделенный распределитель памяти. _Что вы считаете более изящным алгоритмом? _ Да, но алгоритм изящный, проблема заключалась в эффективном кодировании этого элегантного алгоритма. – Esus

+0

Значения в векторе должны быть ints? Могут ли они быть шортами? –

+0

Нет, они не могут ... Я также исследую более разработанные решения с компрессией диапазона, но в настоящее время я все еще на стадии эксперимента. – Esus

2

Кажется, this парень решил вашу проблему очень красиво.

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

Он использует мощь алгоритма блочного шифрования с переменной длиной бита для генерации каждого индекса в массиве.

+0

Действительно, и кажется, что перестановка лучше, чем в предыдущих ответах, потому что она смешивает алгебраические и murmurhash. Я прав? – Esus

+0

Он использует функцию murmurhash как функцию округления в итерациях сети Feistel, но он также заявляет, что вы можете использовать любую другую разработку. Это хеширование просто дало ему хорошие результаты. – NicolaSysnet

Смежные вопросы