我需要用随机值填充一个巨大的(7734500元素)std::vector<unsigned int>
,我试图与多个线程并行地实现它以实现更高的效率。这是我到目前为止的代码:
std::random_device rd; // seed generator
std::mt19937_64 generator{rd()}; // generator initialized with seed from rd
static const unsigned int NUM_THREADS = 4;
std::uniform_int_distribution<> initialize(unsigned long long int modulus)
{
std::uniform_int_distribution<> unifDist{0, (int)(modulus-1)};
return unifDist;
}
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end, std::uniform_int_distribution<>& dist)
{
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = dist(generator);
}
}
std::vector<unsigned int> uniformRandomVector
(unsigned int rows, unsigned int columns, unsigned long long int modulus)
{
std::uniform_int_distribution<> dist = initialize(modulus);
std::thread threads[NUM_THREADS];
std::vector<unsigned int> v;
v.resize(rows*columns);
// number of entries each thread will take care of
unsigned int positionsEachThread = rows*columns/NUM_THREADS;
// all but the last thread
for(unsigned int i = 0 ; i < NUM_THREADS - 1 ; ++i)
{
threads[i] = std::thread(unifRandVectorThreadRoutine, v, i*positionsEachThread,
(i+1)*positionsEachThread, dist);
// threads[i].join();
}
// last thread
threads[NUM_THREADS - 1] = std::thread(unifRandVectorThreadRoutine, v,
(NUM_THREADS-1)*positionsEachThread, rows*columns, dist);
// threads[NUM_THREADS - 1].join();
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
threads[i].join();
}
return v;
}
目前,大约需要0.3秒:您认为有办法提高效率吗?
编辑:为每个线程提供自己的生成器
我修改了例程如下
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end, std::uniform_int_distribution<>& dist)
{
std::mt19937_64 generator{rd()};
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = dist(generator);
}
}
并且运行时间减少了一半。所以我仍在分享std::random_device
,但每个帖子都有自己的std::mt19937_64
。
编辑:为每个帖子提供自己的向量,然后连接
我更改了代码如下:
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int length,
std::uniform_int_distribution<>& dist)
{
vector.reserve(length);
std::mt19937_64 generator{rd()};
for(unsigned int i = 0 ; i < length ; ++i)
{
vector.push_back(dist(generator));
}
}
和
std::vector<unsigned int> uniformRandomVector
(unsigned int rows, unsigned int columns, unsigned long long int modulus)
{
std::uniform_int_distribution<> dist = initialize(modulus);
std::thread threads[NUM_THREADS];
std::vector<unsigned int> v[NUM_THREADS];
unsigned int positionsEachThread = rows*columns/NUM_THREADS;
// all but the last thread
for(unsigned int i = 0 ; i < NUM_THREADS - 1 ; ++i)
{
threads[i] = std::thread(unifRandVectorThreadRoutine, std::ref(v[i]), positionsEachThread, dist);
}
// last thread
threads[NUM_THREADS - 1] = std::thread(unifRandVectorThreadRoutine, std::ref(v[NUM_THREADS-1]),
rows*columns - (NUM_THREADS-1)*positionsEachThread, dist);
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
threads[i].join();
}
std::vector<unsigned int> finalVector;
finalVector.reserve(rows*columns);
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
finalVector.insert(finalVector.end(), v[i].begin(), v[i].end());
}
return finalVector;
}
执行时间比之前略差,当时我只使用了一个在所有线程之间共享的向量。我错过了什么或者它会发生吗?
使用不同的PRNG +基准编辑
使用不同的PRNG(如某些评论/答案中所建议的)有很多帮助:我尝试使用xorshift+
,这是我正在使用的实现:
class xorShift128PlusGenerator
{
public:
xorShift128PlusGenerator()
{
state[0] = rd();
state[1] = rd();
};
unsigned long int next()
{
unsigned long int x = state[0];
unsigned long int const y = state[1];
state[0] = y;
x ^= x << 23; // a
state[1] = x ^ y ^ (x >> 17) ^ (y >> 26); // b, c
return state[1] + y;
}
private:
std::random_device rd; // seed generator
unsigned long int state[2];
};
然后例程如下
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end)
{
xorShift128PlusGenerator prng;
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = prng.next();
}
}
由于我现在在家,而且我正在使用另一台(功能更强大的)机器,我重新测试以比较结果。这是我获得的:
注意:执行时间在每次重复时都会有所不同。这些只是典型值。
因此,如果共享xorshift生成器似乎没有区别,但是通过所有这些改进,执行时间显着下降。
答案 0 :(得分:8)
生成器std::mt19937_64 generator{rd()};
在线程之间共享。将存在一些需要更新的共享状态,因此存在争用;有数据竞争。您还应该考虑允许每个线程使用自己的生成器 - 您只需要确保它们生成单独的序列。
你可能在std::vector<unsigned int> v;
周围有一个缓存争用问题,它在线程之外声明,然后在每个线程中的for循环的每次迭代中命中。让每个线程都有自己的向量来填充,一旦完成所有线程,就将它们的结果整理到向量v
中。可能通过std::future
将是最快的。 争用的确切大小取决于缓存行大小和正在使用的向量(和分段)的大小。
在这种情况下,您使用相对较少的线程(4)填充大量元素(7734500),该比率可能会导致较少的争用。
W.r.t。您可以使用的数字线程,您应该考虑将NUM_THREADS
绑定到目标上可用的硬件并发;即std::thread::hardware_concurrency()
。
在处理大量元素时,您还可以避免不必要的初始化和移动结果(尽管给定int
类型,此处移动不太明显)。容器本身也是值得注意的; vector
需要连续的内存,因此任何其他元素(在联盟阶段)都可能导致内存分配和复制。
随机数生成器的速度也可能产生影响,其他实现和/或算法可能会大大影响最终执行时间以供考虑。
与所有基于绩效的问题一样 - 最终的解决方案需要衡量。实施可能的解决方案,测量目标处理器和环境,并进行调整,直到找到合适的性能。
答案 1 :(得分:3)
Mersenne Twister发电机(std::mt19937_64
)并不太快。你可能会考虑其他生成器,如Xorshift +。参见,例如,这个问题:What is performance-wise the best way to generate random bools?(讨论不仅仅是bool)。
你应该摆脱代码中的数据竞争。每个线程使用一个生成器。
答案 2 :(得分:0)
std::vector<unsigned int> v;
v.resize(rows*columns);
不幸的是,std::vector::resize
值初始化原语,使你的程序一旦在向量存储器上写零,然后用随机数覆盖这个值。
尝试std::vector::reserve
+ std::vector::push_back
这意味着线程不能在没有锁定的情况下共享向量,但是您可以为每个向量分配它自己的向量,使用reserve+push_back
然后将所有结果组合到更大的向量。
如果这还不够,我不想这么说,请std::unique_ptr
使用malloc
(使用服装删除器)。是的,这是C,是的,这是令人讨厌的,是的,我们有new[]
,但是malloc
赢了零内存(不像new[]
和stl容器),那么你可以将内存段传播到每个线程,并让它在其上生成随机数。您将保存向量与一个组合向量的组合。