私は現在、科学的シミュレーション(重力nbody)に取り組んでいます。私は最初にナイーブなシングルスレッドアルゴリズムでそれを書きました、そしてこれは少数の粒子に対して許容できるように実行されました。次に、このアルゴリズムをマルチスレッド化し(驚異的並列)、プログラムの所要時間は約3倍でした。以下は、同様のプロパティを持ち、/ tmp内のファイルに出力される簡単なアルゴリズムの最小限の完全で検証可能な例です(Linuxで実行するように設計されていますが、C ++も標準です)。このコードを実行すると、152.62MBのファイルが生成されることに注意してください。データは、コンパイラがプログラムからの計算を最適化するのを防ぐために出力されます。
#include <iostream>
#include <functional>
#include <thread>
#include <vector>
#include <atomic>
#include <random>
#include <fstream>
#include <chrono>
constexpr unsigned ITERATION_COUNT = 2000;
constexpr unsigned NUMBER_COUNT = 10000;
void runThreaded(unsigned count, unsigned batchSize, std::function<void(unsigned)> callback){
unsigned threadCount = std::thread::hardware_concurrency();
std::vector<std::thread> threads;
threads.reserve(threadCount);
std::atomic<unsigned> currentIndex(0);
for(unsigned i=0;i<threadCount;++i){
threads.emplace_back([¤tIndex, batchSize, count, callback]{
unsigned startAt = currentIndex.fetch_add(batchSize);
if(startAt >= count){
return;
}else{
for(unsigned i=0;i<count;++i){
unsigned index = startAt+i;
if(index >= count){
return;
}
callback(index);
}
}
});
}
for(std::thread &thread : threads){
thread.join();
}
}
void threadedTest(){
std::mt19937_64 rnd(0);
std::vector<double> numbers;
numbers.reserve(NUMBER_COUNT);
for(unsigned i=0;i<NUMBER_COUNT;++i){
numbers.push_back(rnd());
}
std::vector<double> newNumbers = numbers;
std::ofstream fout("/tmp/test-data.bin");
for(unsigned i=0;i<ITERATION_COUNT;++i) {
std::cout << "Iteration: " << i << "/" << ITERATION_COUNT << std::endl;
runThreaded(NUMBER_COUNT, 100, [&numbers, &newNumbers](unsigned x){
double total = 0;
for(unsigned y=0;y<NUMBER_COUNT;++y){
total += numbers[y]*(y-x)*(y-x);
}
newNumbers[x] = total;
});
fout.write(reinterpret_cast<char*>(newNumbers.data()), newNumbers.size()*sizeof(double));
std::swap(numbers, newNumbers);
}
}
void unThreadedTest(){
std::mt19937_64 rnd(0);
std::vector<double> numbers;
numbers.reserve(NUMBER_COUNT);
for(unsigned i=0;i<NUMBER_COUNT;++i){
numbers.push_back(rnd());
}
std::vector<double> newNumbers = numbers;
std::ofstream fout("/tmp/test-data.bin");
for(unsigned i=0;i<ITERATION_COUNT;++i){
std::cout << "Iteration: " << i << "/" << ITERATION_COUNT << std::endl;
for(unsigned x=0;x<NUMBER_COUNT;++x){
double total = 0;
for(unsigned y=0;y<NUMBER_COUNT;++y){
total += numbers[y]*(y-x)*(y-x);
}
newNumbers[x] = total;
}
fout.write(reinterpret_cast<char*>(newNumbers.data()), newNumbers.size()*sizeof(double));
std::swap(numbers, newNumbers);
}
}
int main(int argc, char *argv[]) {
if(argv[1][0] == 't'){
threadedTest();
}else{
unThreadedTest();
}
return 0;
}
これを実行すると(Linuxでclang 7.0.1を使用してコンパイル)、Linuxtime
コマンドから次の時間が得られます。これらの違いは、実際のプログラムで見られるものと似ています。「実際の」というラベルの付いたエントリは、プログラムの実行にかかる時間であるため、この質問に関連するものです。
シングルスレッド:
real 6m27.261s
user 6m27.081s
sys 0m0.051s
マルチスレッド:
real 14m32.856s
user 216m58.063s
sys 0m4.492s
そのため、大幅に高速化すると予想される場合、この大幅な速度低下の原因を尋ねます(8コアの16スレッドCPUを使用しているため、約8倍)。次のステップはアルゴリズムにいくつかの変更を加えてO(n²)からO(nlogn)に変更することであるため、これをGPUに実装していませんが、これもGPUには適していません。変更されたアルゴリズムは、含まれている例よりも、現在実装されているO(n²)アルゴリズムとの違いが少なくなります。最後に、各反復を実行するための主観的な時間(反復行が表示される間の時間によって判断される)が、スレッド実行と非スレッド実行の両方で大幅に変化することを確認したいと思います。
このコードに従うのはちょっと難しいですが、各スレッドがほとんどすべての作業を実行し、最初にその一部をスキップするだけなので、大規模に作業を複製していると思います。
の内側のループrunThreaded
は次のようになると思います。
unsigned startAt = currentIndex.fetch_add(batchSize);
while (startAt < count) {
if (startAt >= count) {
return;
} else {
for(unsigned i=0;i<batchSize;++i){
unsigned index = startAt+i;
if(index >= count){
return;
}
callback(index);
}
}
startAt = currentIndex.fetch_add(batchSize);
}
ここi < batchSize
で鍵はどこにありますか。count
リスト全体から初期オフセットを差し引いた時間ではなく、バッチが指示する量の作業のみを実行する必要があります。
この更新により、コードの実行速度が大幅に向上します。それが実際に起こっているかどうかを判断するのは難しいので、必要なすべての作業を実行できるかどうかはわかりません。出力はごくわずかです。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加