2

ロックではなくアトミックを使用して何かを作成し、私の場合は非常に遅いことに当惑し、次のミニテストを作成しました。

#include <pthread.h>
#include <vector>

struct test
{
    test(size_t size) : index_(0), size_(size), vec2_(size)
        {
            vec_.reserve(size_);
            pthread_mutexattr_init(&attrs_);
            pthread_mutexattr_setpshared(&attrs_, PTHREAD_PROCESS_PRIVATE);
            pthread_mutexattr_settype(&attrs_, PTHREAD_MUTEX_ADAPTIVE_NP);

            pthread_mutex_init(&lock_, &attrs_);
        }

    void lockedPush(int i);
    void atomicPush(int* i);

    size_t              index_;
    size_t              size_;
    std::vector<int>    vec_;
    std::vector<int>    vec2_;
    pthread_mutexattr_t attrs_;
    pthread_mutex_t     lock_;
};

void test::lockedPush(int i)
{
    pthread_mutex_lock(&lock_);
    vec_.push_back(i);
    pthread_mutex_unlock(&lock_);
}

void test::atomicPush(int* i)
{
    int ii       = (int) (i - &vec2_.front());
    size_t index = __sync_fetch_and_add(&index_, 1);
    vec2_[index & (size_ - 1)] = ii;
}

int main(int argc, char** argv)
{
    const size_t N = 1048576;
    test t(N);

//     for (int i = 0; i < N; ++i)
//         t.lockedPush(i);

    for (int i = 0; i < N; ++i)
        t.atomicPush(&i);
}

atomicPush 操作のコメントを外してテストを実行すると、次のtime(1)ような出力が得られます。

real    0m0.027s
user    0m0.022s
sys     0m0.005s

そして、アトミックなものを呼び出すループを実行すると(関数をより大きなコードと同じようにできるだけ見えるようにしたいので、一見不要な操作があります)、次のような出力が得られます。

real    0m0.046s
user    0m0.043s
sys     0m0.003s

この場合、アトミックがロックよりも高速であると予想していたので、なぜこれが起こっているのかわかりません...

-O3 でコンパイルすると、次のようにロックとアトミック更新が表示されます。

lock:
    real    0m0.024s
    user    0m0.022s
    sys     0m0.001s

atomic:    
    real    0m0.013s
    user    0m0.011s
    sys     0m0.002s

私のより大きなアプリでは、ロックのパフォーマンス(シングルスレッドテスト)は依然として改善されていますが..

4

2 に答える 2

6

非競合ミューテックスは、ロックとロック解除が非常に高速です。アトミック変数を使用すると、常に一定のメモリ同期ペナルティが発生します (特に、緩和された順序付けを使用していないため)。

あなたのテストケースは単純すぎて役に立ちません。激しく競合するデータ アクセス シナリオをテストする必要があります。

一般に、アトミック低速ですが (巧妙な内部の並べ替え、パイプライン処理、およびキャッシングの邪魔になります)、ロックフリー コードを使用できるため、プログラム全体がある程度進行することが保証されます。対照的に、ロックを保持している間にスワップアウトすると、全員が待たなければなりません。

于 2012-09-19T16:03:52.057 に答える
1

最初の答えに追加するだけで、__sync_fetch_and_add実際に特定のコード順序を強制する場合があります。ドキュメントから

この関数が呼び出されると、完全なメモリ バリアが作成されます

メモリバリアはいつです

バリア命令の前後に発行されるメモリ操作に順序制約を適用するための中央処理装置 (CPU) またはコンパイラ

作業がアトミックであっても、命令の順序付けを強制することにより、コンパイラの最適化が失われている可能性があります。

于 2012-09-19T16:08:57.663 に答える