Эффективная генерация случайных чисел с С++ 11 <random>
Я пытаюсь понять, как предполагается использовать функции генерации случайных чисел С++ 11. Меня беспокоит производительность.
Предположим, что нам нужно создать ряд случайных чисел между 0..k
, но k
изменяется на каждом шаге. Каков наилучший способ продолжения?
Пример:
for (int i=0; i < n; ++i) {
int k = i; // of course this is more complicated in practice
std::uniform_int_distribution<> dist(0, k);
int random_number = dist(engine);
// do something with random number
}
Распределения, которые предоставляет заголовок <random>
, очень удобны. Но они непрозрачны для пользователя, поэтому я не могу легко предсказать, как они будут работать. Не ясно, например, сколько (если таковые имеются) служебных данных времени выполнения будут вызваны конструкцией dist
выше.
Вместо этого я мог бы использовать что-то вроде
std::uniform_real_distribution<> dist(0.0, 1.0);
for (int i=0; i < n; ++i) {
int k = i; // of course this is more complicated in practice
int random_number = std::floor( (k+1)*dist(engine) );
// do something with random number
}
который позволяет избежать создания нового объекта на каждой итерации.
Случайные числа часто используются в численных симуляциях, где важна производительность. Каков наилучший способ использования <random>
в этих ситуациях?
Пожалуйста, не отвечайте "profile it". Профилирование является частью эффективной оптимизации, но это хорошее понимание того, как предполагается использовать библиотеку и характеристики производительности этой библиотеки. Если ответ заключается в том, что он зависит от стандартной реализации библиотеки или что единственный способ узнать, это профилировать его, то я бы предпочел не использовать дистрибутивы из <random>
вообще. Вместо этого я могу использовать свою собственную реализацию, которая будет прозрачной для меня и намного легче оптимизировать, если при необходимости.
Ответы
Ответ 1
Одна вещь, которую вы можете сделать, - иметь постоянный объект распространения, чтобы каждый раз создавать объект param_type
следующим образом:
template<typename Integral>
Integral randint(Integral min, Integral max)
{
using param_type =
typename std::uniform_int_distribution<Integral>::param_type;
// only create these once (per thread)
thread_local static std::mt19937 eng {std::random_device{}()};
thread_local static std::uniform_int_distribution<Integral> dist;
// presumably a param_type is cheaper than a uniform_int_distribution
return dist(eng, param_type{min, max});
}
Ответ 2
Для максимальной производительности, прежде всего, рассмотрим разные PRNG, такие как xorshift128 +. Сообщается, что он более чем в два раза быстрее, чем mt19937
для 64-битных случайных чисел; см. http://xorshift.di.unimi.it/. И он может быть реализован с несколькими строками кода.
Кроме того, если вам не нужно "идеально сбалансированное" равномерное распределение, а ваш k
намного меньше 2^64
(что, вероятно, так), я бы предложил написать просто что-то вроде:
uint64_t temp = engine_64(); // generates 0 <= temp < 2^64
int random_number = temp % (k + 1); // crop temp to 0,...,k
Обратите внимание, однако, что операции с целым делением/модулем не являются дешевыми. Например, на процессоре Intel Haswell они принимают 39-103 процессорных цикла для 64-битных чисел, что, вероятно, намного дольше, чем вызов MT19937 или xorshift + engine.