2016-08-26 8 views
4

私は、Windowsマシン(MSVS2015を使用してコンパイルされたもの)とSolaris 10を実行するサーバー(GCC 4.9.3を使用してコンパイルされたもの)の両方で簡単なスレッドテストプログラムを実行しています。 Windowsでは、スレッドを1から利用可能なコアの量に増やすことでパフォーマンスが大幅に向上します。しかし、Solaris 10ではまったく同じコードではパフォーマンスの向上は見られません。WindowsとSolaris 10でのstd :: asyncパフォーマンス

Windowsマシンには4つのコア(8論理)があり、UNIXマシンには8コア(16論理)があります。

この原因は何ですか? -pthreadでコンパイルしています。です。最初の "F"の前にすべての "S"が表示されるため、スレッドを作成しています。私はSolarisマシン上でrootアクセス権を持っておらず、プロセスの親和性を見るために使用できるインストールされたツールはありません。

例コード:原則として

#include <iostream> 
#include <vector> 
#include <future> 
#include <random> 
#include <chrono> 

std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count()); 
std::normal_distribution<double> randn(0.0, 1.0); 

double generate_randn(uint64_t iterations) 
{ 
    // Print "S" when a thread starts 
    std::cout << "S"; 
    std::cout.flush(); 

    double rvalue = 0; 
    for (int i = 0; i < iterations; i++) 
    { 
     rvalue += randn(gen); 
    } 
    // Print "F" when a thread finishes 
    std::cout << "F"; 
    std::cout.flush(); 

    return rvalue/iterations; 
} 

int main(int argc, char *argv[]) 
{ 
    if (argc < 2) 
     return 0; 

    uint64_t count = 100000000; 
    uint32_t threads = std::atoi(argv[1]); 

    double total = 0; 

    std::vector<std::future<double>> futures; 
    std::chrono::high_resolution_clock::time_point t1; 
    std::chrono::high_resolution_clock::time_point t2; 

    // Start timing 
    t1 = std::chrono::high_resolution_clock::now(); 
    for (int i = 0; i < threads; i++) 
    { 
     // Start async tasks 
     futures.push_back(std::async(std::launch::async, generate_randn, count/threads)); 
    } 
    for (auto &future : futures) 
    { 
     // Wait for tasks to finish 
     future.wait(); 
     total += future.get(); 
    } 
    // End timing 
    t2 = std::chrono::high_resolution_clock::now(); 

    // Take the average of the threads' results 
    total /= threads; 

    std::cout << std::endl; 
    std::cout << total << std::endl; 
    std::cout << "Finished in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms" << std::endl; 
} 
+0

'gen'と' randn'の宣言を 'generate_randn'の中で動かしてみましょう。一つの共有RNGの代わりにスレッドごとに一つのRNGインスタンスがあるようにしてください。私はまた、スレッドごとのランタイムの完全な分布を見ることに興味があります。 – zwol

+0

genとrandnをgenerate_randnの中に移動した後、正常に動作しました!それを答えとして加えて、それをマークします。 :) –

答えて

3

、C++標準ライブラリによって定義されたクラスは、ないは、任意の内部ロックを持っています。 2つ以上のスレッドから標準ライブラリクラスのインスタンスを変更するか、別のスレッドから書き込み中にあるスレッドからそのスレッドを読み取ることは、「そのタイプのオブジェクトがデータ競合なしで共有可能であると明示的に指定されていない限り(N3337、セクション17.6.4.10および17.6.5.9。)RNGクラスは、「データ競合なしで共有可能であると明示的に指定されていません」。 (cout「データ競合と共有可能」であることをSTDLIBオブジェクトの一例である - 。限り、あなたはios::sync_with_stdio(false)を行っていないとして)このように

、それはグローバルRNGにアクセスするので、あなたのプログラムが間違ってです同時に複数のスレッドからのオブジェクト。別の乱数を要求するたびに、ジェネレータの内部状態が変更されます。 Solarisでは、これがアクセスのシリアライズを引き起こすようですが、Windowsではおそらく代わりに適切に「ランダム」な数字を取得しないようにしています。

解決策は、スレッドごとに別々のRNGを作成することです。次に、各スレッドは独立して動作し、互いの速度を下げたり、お互いの足を踏み外したりしません。これは、非常に一般的な原則の特殊なケースです。マルチスレッドは、共有されるデータが少ないほど、常に良好に機能します。

心配する追加のシワがあります:あなた同じ値を播種スレッド単位のRNGの一部になっている可能性がありので、各スレッドは、非常にほぼ同時にsystem_clock::nowを呼び出します。 random_deviceオブジェクトからそれらをすべてシードする方が良いでしょう。 random_deviceは、オペレーティングシステムから乱数を要求し、シードする必要はありません。それは非常に遅くなることがあります。 random_deviceは、main内に作成して使用し、各ワーカー関数にシードを渡す必要があります。これは、複数のスレッドからアクセスされたグローバルrandom_deviceは、グローバルdefault_random_engineと同じように定義されていません。

すべてはあなたのプログラムは次のようになります、言いました:

#include <iostream> 
#include <vector> 
#include <future> 
#include <random> 
#include <chrono> 

static double generate_randn(uint64_t iterations, unsigned int seed) 
{ 
    // Print "S" when a thread starts 
    std::cout << "S"; 
    std::cout.flush(); 

    std::default_random_engine gen(seed); 
    std::normal_distribution<double> randn(0.0, 1.0); 

    double rvalue = 0; 
    for (int i = 0; i < iterations; i++) 
    { 
     rvalue += randn(gen); 
    } 
    // Print "F" when a thread finishes 
    std::cout << "F"; 
    std::cout.flush(); 

    return rvalue/iterations; 
} 

int main(int argc, char *argv[]) 
{ 
    if (argc < 2) 
     return 0; 

    uint64_t count = 100000000; 
    uint32_t threads = std::atoi(argv[1]); 

    double total = 0; 

    std::vector<std::future<double>> futures; 
    std::chrono::high_resolution_clock::time_point t1; 
    std::chrono::high_resolution_clock::time_point t2; 

    std::random_device make_seed; 

    // Start timing 
    t1 = std::chrono::high_resolution_clock::now(); 
    for (int i = 0; i < threads; i++) 
    { 
     // Start async tasks 
     futures.push_back(std::async(std::launch::async, 
            generate_randn, 
            count/threads, 
            make_seed())); 
    } 
    for (auto &future : futures) 
    { 
     // Wait for tasks to finish 
     future.wait(); 
     total += future.get(); 
    } 
    // End timing 
    t2 = std::chrono::high_resolution_clock::now(); 

    // Take the average of the threads' results 
    total /= threads; 

    std::cout << '\n' << total 
       << "\nFinished in " 
       << std::chrono::duration_cast< 
        std::chrono::milliseconds>(t2 - t1).count() 
       << " ms\n"; 
} 
+0

_ Solarisのの実装に内部ロックがあります_ _、FWIWは、Solarisまたは他のプラットフォームの ' 'にロックされていないGCC 4.9を示しています。私は共有エンジン+ディストリビューションがなぜそんなに遅いのか分かりませんが、とにかくUBだから私はそれほど気にしません。スレッドごとに1つを使用することは正しさのために必要です。それでパフォーマンス上の問題がうまくいくなら:-) –

+0

@JonathanWakely現象についての別の説明がありますか?私は、SolarisまたはWindowsのどちらでも簡単に実験することはできません。 – zwol

+0

異なるスレッドが異なるプロセッサ上でスケジュールされており、共有変数のキャッシュミスを絶えず取得しているため、キャッシュの競合が効果的にシリアルに実行されるようにするのが最も良い方法です。 –

2

(これは本当に答えではないが、それは特に、リンクをフォーマットするコマンドを使用して、コメントに収まらないだろう。)

Solaris Studio's collect utilityを使用してSolaris上で実行可能ファイルをプロファイルできます。 Solarisでは、スレッドが競合している場所を表示できます。

collect -d /tmp -p high -s all app [app args] 

次にthe analyzer utilityを使用して結果を表示する:

analyzer /tmp/test.1.er & 

collectプロファイルの実行によって生成された出力へのパスと/tmp/test.1.erを交換します。

あなたのスレッドが答えに投稿された@zwolのようにいくつかのリソースに対して競合している場合は、それが表示されます。ツールセットのための

オラクル・マーケティング・ブリーフでは、ここで見つけることができます:http://www.oracle.com/technetwork/server-storage/solarisstudio/documentation/o11-151-perf-analyzer-brief-1405338.pdf

また、より多くのデータについては、Solaris Studioで、あなたのコードをコンパイルしてみてください。

+0

問題は既に解決されていますが、返信いただきありがとうございます!私が問題を抱えていた実際のアプリケーションにSolaris Studioを使用することは、不可能であったため、GCCでテストしました。私は間違いなく私は後で同様の問題に遭遇する場合、アナライザーを使用します! –

関連する問題