我需要std::vector<unsigned int>
用随机值填充一个巨大的(7734500个元素),并且我试图与多个线程并行执行以实现更高的效率。这是我到目前为止的代码:
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生成器似乎没有什么区别,但是通过所有这些改进,执行时间大大减少了。
生成器std::mt19937_64 generator{rd()};
在线程之间共享。将存在一些需要更新的共享状态,因此存在争用;有一场数据竞赛。您还应该允许每个线程使用自己的生成器-您只需要确保它们生成单独的序列即可。
您可能在附近有一个缓存争用问题std::vector<unsigned int> v;
,它在线程外声明,然后在每个线程中的for循环的每次迭代中都命中。让每个线程都有其自己的向量来填充,一旦所有线程都完成,就将它们的结果整理到vector中v
。可能通过astd::future
最快。争用的确切大小取决于高速缓存行大小和正在使用(和分段)的向量的大小。
在这种情况下,用相对较少的线程(4)填充大量元素(7734500),该比率可能会导致较少的争用。
要使用可能使用的线程数,应考虑将其绑定NUM_THREADS
到目标上可用的硬件并发;即std::thread::hardware_concurrency()
。
在处理大量元素时,您还可以避免不必要的初始化和结果的移动(尽管给定了int
类型,但移动在这里不太明显)。容器本身也是需要注意的事情。vector
需要连续的内存,因此(在合并阶段)任何其他元素都可能导致内存分配和复制。
随机数生成器的速度也可能会产生影响,其他实现和/或算法可能会充分影响最终执行时间以至于需要考虑。
与所有基于性能的问题一样,最终解决方案需要评估。实施可能的解决方案,在目标处理器和环境上进行测量,并进行调整,直到找到合适的性能为止。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句