如何在C+;中高效地生成排序的均匀分布随机数+;? < >我想生成大量的,C++中的排序和均匀分布随机数的代码< n>代码>,即 n>=1000000000 < /> > >
我考虑的第一个简单方法是如何在C+;中高效地生成排序的均匀分布随机数+;? < >我想生成大量的,C++中的排序和均匀分布随机数的代码< n>代码>,即 n>=1000000000 < /> > > ,c++,algorithm,sorting,random,c++17,C++,Algorithm,Sorting,Random,C++17,我考虑的第一个简单方法是 使用std::uniform_real_distribution按顺序生成n均匀分布的数字 然后使用std::sort对它们进行排序 然而,这需要几分钟 第二种也是更复杂的方法是将两个步骤并行化,如下所示: template <typename T> void computeUniformDistribution(std::vector<T>& elements) { #pragma omp parallel {
std::uniform_real_distribution
按顺序生成n
均匀分布的数字std::sort
对它们进行排序template <typename T>
void computeUniformDistribution(std::vector<T>& elements)
{
#pragma omp parallel
{
std::seed_seq seed{distribution_seed, static_cast<size_t>(omp_get_thread_num())};
std::mt19937 prng = std::mt19937(seed);
std::uniform_real_distribution<double> uniform_dist(0, std::numeric_limits<T>::max());
#pragma omp for
for (size_t i = 0; i < elements.size(); ++i)
{
elements[i] = static_cast<T>(uniform_dist(prng));
}
}
std::sort(std::execution::par_unseq, elements.begin(), elements.end());
}
模板
无效计算分布(标准::向量和元素)
{
#pragma-omp并行
{
std::seed_seq seed{distribution_seed,static_cast(omp_get_thread_num())};
标准::mt19937 prng=标准::mt19937(种子);
标准::均匀实分布均匀分布(0,标准::数值极限::max());
#pragma omp for
对于(size_t i=0;i
然而,即使这样也需要大约30秒。考虑到生成均匀分布的数字只需约1.5秒,瓶颈仍然是排序阶段
因此,我想问以下问题:如何有效地以排序方式生成均匀分布的数据?有一些方法可以生成已排序的样本,但我认为生成部分排序的样本可能更好 将输出范围划分为等宽的k个桶。每个桶中的样本数将具有概率相等的多项式分布。对多项式分布进行采样的慢方法是在[0,k]中生成n个整数。更有效的方法是以n/k的和不超过n为条件绘制k个泊松样本,然后使用慢方法添加另一个n和样本。泊松分布的采样很难做到完美,但当n/k非常大时(如本文所示),泊松分布通过将正态分布与均值和方差n/k四舍五入而得到了很好的近似。如果这是不可接受的,那么慢速方法也可以很好地并行 给定bucket计数,计算前缀和以找到bucket边界。对于并行的每个bucket,生成bucket范围内给定数量的样本并对其进行排序。如果我们选择n/k,每个bucket几乎肯定适合一级缓存。对于n=1e9,我想我会尝试k=1e5或k=1e6 这是一个顺序实现。有点不完善,因为我们确实需要避免对桶边界进行2倍的过采样,这些边界是闭合的,但我将留给您。我不熟悉OMP,但我认为您可以通过在
SortedUniformSamples
末尾的for循环中添加pragma来获得一个相当好的并行实现>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <numeric>
#include <random>
#include <span>
#include <vector>
template <typename Dist, typename Gen>
void SortedSamples(std::span<double> samples, Dist dist, Gen& gen) {
for (double& sample : samples) {
sample = dist(gen);
}
std::sort(samples.begin(), samples.end());
}
template <typename Gen>
void ApproxMultinomialSample(std::span<std::size_t> samples, std::size_t n,
Gen& gen) {
double lambda = static_cast<double>(n) / samples.size();
std::normal_distribution<double> approx_poisson{lambda, std::sqrt(lambda)};
std::size_t sum;
do {
for (std::size_t& sample : samples) {
sample = std::lrint(approx_poisson(gen));
}
sum = std::accumulate(samples.begin(), samples.end(), std::size_t{0});
} while (sum > n);
std::uniform_int_distribution<std::size_t> uniform{0, samples.size() - 1};
for (; sum < n; sum++) {
samples[uniform(gen)]++;
}
}
template <typename Gen>
void SortedUniformSamples(std::span<double> samples, Gen& gen) {
static constexpr std::size_t kTargetBucketSize = 1024;
if (samples.size() < kTargetBucketSize) {
SortedSamples(samples, std::uniform_real_distribution<double>{0, 1}, gen);
return;
}
std::size_t num_buckets = samples.size() / kTargetBucketSize;
std::vector<std::size_t> bucket_counts(num_buckets);
ApproxMultinomialSample(bucket_counts, samples.size(), gen);
std::vector<std::size_t> prefix_sums(num_buckets + 1);
std::partial_sum(bucket_counts.begin(), bucket_counts.end(),
++prefix_sums.begin());
for (std::size_t i = 0; i < num_buckets; i++) {
SortedSamples(std::span<double>{&samples[prefix_sums[i]],
&samples[prefix_sums[i + 1]]},
std::uniform_real_distribution<double>{
static_cast<double>(i) / num_buckets,
static_cast<double>(i + 1) / num_buckets},
gen);
}
}
int main() {
std::vector<double> samples(100000000);
std::default_random_engine gen;
SortedUniformSamples(samples, gen);
if (std::is_sorted(samples.begin(), samples.end())) {
std::cout << "sorted\n";
}
}
我运行了一些测试,根据系统的不同,基数排序的速度是std::sort的4到6倍,但它需要第二个向量,对于1GB的元素,每个双精度向量是8GB,总共有16GB的可用内存,因此可能需要32GB的RAM 如果排序不受内存带宽限制,则多线程基数排序可能会有所帮助 单线程代码示例:
#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
#include <time.h>
clock_t ctTimeStart; // clock values
clock_t ctTimeStop;
typedef unsigned long long uint64_t;
// a is input array, b is working array
uint64_t * RadixSort(uint64_t * a, uint64_t *b, size_t count)
{
uint32_t mIndex[8][256] = {0}; // count / index matrix
uint32_t i,j,m,n;
uint64_t u;
for(i = 0; i < count; i++){ // generate histograms
u = a[i];
for(j = 0; j < 8; j++){
mIndex[j][(size_t)(u & 0xff)]++;
u >>= 8;
}
}
for(j = 0; j < 8; j++){ // convert to indices
m = 0;
for(i = 0; i < 256; i++){
n = mIndex[j][i];
mIndex[j][i] = m;
m += n;
}
}
for(j = 0; j < 8; j++){ // radix sort
for(i = 0; i < count; i++){ // sort by current LSB
u = a[i];
m = (size_t)(u>>(j<<3))&0xff;
b[mIndex[j][m]++] = u;
}
std::swap(a, b); // swap ptrs
}
return(a);
}
#define COUNT (1024*1024*1024)
int main(int argc, char**argv)
{
std::vector<double> v(COUNT); // vctr to be generated
std::vector<double> t(COUNT); // temp vector
std::random_device rd;
std::mt19937 gen(rd());
// std::uniform_real_distribution<> dis(0, std::numeric_limits<double>::max());
std::uniform_real_distribution<> dis(0, COUNT);
ctTimeStart = clock();
for(size_t i = 0; i < v.size(); i++)
v[i] = dis(gen);
ctTimeStop = clock();
std::cout << "# of ticks " << ctTimeStop - ctTimeStart << std::endl;
ctTimeStart = clock();
// std::sort(v.begin(), v.end());
RadixSort((uint64_t *)&v[0], (uint64_t *)&t[0], COUNT);
ctTimeStop = clock();
std::cout << "# of ticks " << ctTimeStop - ctTimeStart << std::endl;
return(0);
}
我倾向于依赖这样一个事实,即一组已排序的均匀分布变量的连续元素之间的差异呈指数分布。这可以被利用来在
O(N)
时间内运行,而不是O(N*logn)
快速实施将执行以下操作:
template<typename T> void
computeSorteUniform2(std::vector<T>& elements)
{
std::random_device rd;
std::mt19937 prng(rd());
std::exponential_distribution<T> dist(static_cast<T>(1));
auto sum = dist(prng);
for (auto& elem : elements) {
elem = sum += dist(prng);
}
sum += dist(prng);
for (auto& elem : elements) {
elem /= sum;
}
}
模板无效
computeSorteUniform2(标准::向量和元素)
{
std::随机_装置rd;
标准:mt19937 prng(rd());
指数分布区(静态分布(1));
自动求和=距离(prng);
用于(自动和元素:元素){
elem=总和+=距离(prng);
}
总和+=距离(prng);
用于(自动和元素:元素){
元素/=总和;
}
}
假设您想要统一(0,1)中的值,可以简化此示例,但它应该很容易推广。使用OMP进行此工作不是很简单,但也不应该太难
如果您关心最后50%的性能,有一些数字技巧可能会加快生成随机偏差(例如,PRNG比MT更快更好),并将其转换为double
s(但最近的编译器可能知道这些技巧)。参考文献:和
我刚刚对此进行了基准测试,发现clang的
std::uniform\u real\u distribution
和std::exponential\u distribution
都非常慢。速度快了8倍,这样我就可以在笔记本电脑上用一个线程在约10秒内生成1e9double
(即std
实现大约需要80秒)使用上述算法。我没有在1e9元素上尝试过OP的实现,但在1e8元素上,我的实现速度快了约15倍。有一个简单的观察,涉及[0,1]中的排序均匀随机数:
// converting doubles to unsigned long long for radix sort or something similar
// note -0 converted to 0x7fffffffffffffff, +0 converted to 0x8000000000000000
// -0 is unlikely to be produced by a float operation
#define SM2ULL(x) ((x)^(((~(x) >> 63)-1) | 0x8000000000000000ull))
#define ULL2SM(x) ((x)^((( (x) >> 63)-1) | 0x8000000000000000ull))
template<typename T> void
computeSorteUniform2(std::vector<T>& elements)
{
std::random_device rd;
std::mt19937 prng(rd());
std::exponential_distribution<T> dist(static_cast<T>(1));
auto sum = dist(prng);
for (auto& elem : elements) {
elem = sum += dist(prng);
}
sum += dist(prng);
for (auto& elem : elements) {
elem /= sum;
}
}