【发布时间】:2016-06-03 17:43:23
【问题描述】:
我需要用随机值填充一个巨大的(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(如某些 cmets/answers 中所建议的)有很大帮助:我尝试使用 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();
}
}
由于我现在在家并且使用的是不同的(并且功能更强大的)机器,因此我重新进行了测试以比较结果。这是我得到的:
- Mersenne Twister,每个线程一个发生器:0.075 秒
- xorshift128+ 在所有线程之间共享:0.023 秒
- xorshift128+ 每个线程一个生成器:0.023 秒
注意:每次重复的执行时间都不同。这些只是典型值。
因此,xorshift 生成器是否共享似乎没有区别,但通过所有这些改进,执行时间已显着下降。
【问题讨论】:
-
为什么你一创建线程就
join?这与按顺序执行基本相同。 -
另外,每个线程使用一个 RNG 可能比共享一个 RNG 更好。
-
您在
generator上存在竞争,因为从多个线程访问不同步。让它thread_local吧? -
PRNG 有状态,因此通常不是线程安全的。
-
而不是
NUM_THREADS = 4;试试NUM_THREADS = std::thread::hardware_concurrency();,也就是不去猜测核心数(有些核心支持超线程)
标签: c++ multithreading c++11