5

コンパイラ: Linux での clang++ x86-64。

複雑な低レベル システム コードを作成してからしばらく経ち、通常はシステム プリミティブ (windows および pthreads/posix) に対してプログラムを作成しています。だから、in#s と out は私の記憶から抜け落ちました。私は現在、と一緒に働いていboost::asioますboost::thread

非同期関数エグゼキューターに対して同期 RPC をエミュレートするために (boost::io_service複数のスレッドio::service::runでリクエストが実行されるio_serviced::post)、ブースト同期プリミティブを使用しています。好奇心のために、私sizeofはプリミティブに決めました。これは私が見るものです。

struct notification_object
{
  bool ready;
  boost::mutex m;
  boost::condition_variable v;
};
...
std::cout << sizeof(bool) << std::endl;
std::cout << sizeof(boost::mutex) << std::endl;
std::cout << sizeof(boost::condition_variable) << std::endl;
std::cout << sizeof(notification_object) << std::endl;
...

出力:

1
40
88
136

ミューテックスの 40 バイト ?? ?? ? なんてこった!condition_variable の場合は 88 !!! 私は何百notification_objectもの

移植性のためのこのレベルのオーバーヘッドはばかげているように思えますが、誰かがこれを正当化できますか? 私が覚えている限り、これらのプリミティブは、CPU のメモリ モデルに応じて 4 または 8 バイト幅である必要があります。

4

4 に答える 4

25

任意のタイプの同期プリミティブの「サイズ オーバーヘッド」を見るときは、これらをあまり密に詰めることはできないことに注意してください。これは、たとえば、キャッシュラインを共有する 2 つのミューテックスが同時に使用されている場合、これらのロックを取得するユーザーが「競合」しない場合でも、キャッシュの破棄 (偽の共有) になるためです。つまり、2 つのスレッドが 2 つのループを実行しているとします。

for (;;) {
    lock(lockA);
    unlock(lockA);
}

for (;;) {
    lock(lockB);
    unlock(lockB);
}

2 つのロックが同じキャッシュライン内にない場合に限り、 1 つのループを実行する 1 つのスレッドと比較して、2 つの異なるスレッドで実行すると、2 倍の反復回数が表示されます。とが同じキャッシュラインにある場合、スレッドごとの反復回数は半分になります。これは、これら 2 つのロックを含むキャッシュラインが、これら 2 つのスレッドを実行する CPU コア間で永続的にバウンスするためです。lockAlockB

したがって、スピンロックまたはミューテックスの基礎となるプリミティブ データ型の実際のデータ サイズが 1 バイトまたは 32 ビット ワードにすぎない場合でも、そのようなオブジェクトの有効なデータ サイズは多くの場合、より大きくなります。

「私のミューテックスが大きすぎる」と主張する前に、そのことを心に留めておいてください。実際、x86/x64 では、キャッシュラインには現在少なくとも 64 バイトがあるため、40 バイトは小さすぎて偽の共有を防ぐことができません。

それを超えて、メモリ使用量を非常に懸念している場合は、通知オブジェクトが一意である必要はないことを考慮してください。条件変数は、さまざまなイベントをトリガーするのに役立ちます (知っている を介して) predicateboost::condition_variableしたがって、ステートごとに 1 つのペアを使用する代わりに、ステート マシン全体に対して 1 つのミューテックス/CV ペアを使用することが可能です。スレッドプールの同期などについても同様です。スレッドよりも多くのロックを持つことは、必ずしも有益ではありません。

編集:「偽共有」(および同じキャッシュライン内で複数のアトミックに更新された変数をホストすることによって引き起こされるパフォーマンスへの悪影響)に関するいくつかの参照については、(特に)次の SO 投稿を参照してください。

前述のように、マルチコアのコアごとのキャッシュ構成で複数の「同期オブジェクト」(アトミックに更新される変数、ロック、セマフォなど) を使用する場合は、それぞれに個別のキャッシュラインを許可します。スペース。ここではスケーラビリティのためにメモリ使用量をトレードしていますが、実際には、ソフトウェアが数百万のロックを必要とする領域 (メモリの GB を作成する) に到達した場合、数百 GB のメモリ (および数百の CPU コア)、またはソフトウェア設計で何か間違ったことをしている可能性があります。

ほとんどの場合 ( / の特定のインスタンスに対するロック / アトミックclass) struct、アトミック変数を含むオブジェクト インスタンスが十分に大きい限り、無料で「パディング」を取得します。

于 2011-07-25T16:22:24.307 に答える
19

私の64ビットUbuntuボックスでは、次のようになります。

#include <pthread.h>
#include <stdio.h>

int main() {
  printf("sizeof(pthread_mutex_t)=%ld\n", sizeof(pthread_mutex_t));
  printf("sizeof(pthread_cond_t)=%ld\n", sizeof(pthread_cond_t));
  return 0;
}

プリント

sizeof(pthread_mutex_t)=40
sizeof(pthread_cond_t)=48

これは、あなたの主張が

移植性のためのこのレベルのオーバーヘッドはばかげているようです、誰かが私にこれを正当化できますか?私が覚えている限り、これらのプリミティブは、CPUのメモリモデルに応じて4バイトまたは8バイト幅である必要があります。

まったく真実ではありません。

余分な40バイトがどこから来ているのか疑問に思われる場合はboost::condition_variable、Boostクラスは内部ミューテックスを使用します。

一言で言えば、このプラットフォームでは、と比較してオーバーヘッドboost::mutexがまったくなく、追加の内部ミューテックスのオーバーヘッドがあります後者がアプリケーションに受け入れられるかどうかは、あなたが決めることです。pthread_mutex_tboost::condition_variable

PS私はあなたが事実に固執し、あなたの投稿で炎症性の言葉を使用することを避けることをお勧めします。私は、純粋にその口調のためにあなたの投稿を無視することをほぼ決心しました。

于 2011-07-25T13:07:14.177 に答える
6

実装を見る:

class mutex : private noncopyable
{
public:
    friend class detail::thread::lock_ops<mutex>;

    typedef detail::thread::scoped_lock<mutex> scoped_lock;

    mutex();
    ~mutex();

private:
#if defined(BOOST_HAS_WINTHREADS)
    typedef void* cv_state;
#elif defined(BOOST_HAS_PTHREADS)
    struct cv_state
    {
        pthread_mutex_t* pmutex;
    };
#elif defined(BOOST_HAS_MPTASKS)
    struct cv_state
    {
    };
#endif
    void do_lock();
    void do_unlock();
    void do_lock(cv_state& state);
    void do_unlock(cv_state& state);

#if defined(BOOST_HAS_WINTHREADS)
    void* m_mutex;
#elif defined(BOOST_HAS_PTHREADS)
    pthread_mutex_t m_mutex;
#elif defined(BOOST_HAS_MPTASKS)
    threads::mac::detail::scoped_critical_region m_mutex;
    threads::mac::detail::scoped_critical_region m_mutex_mutex;
#endif
};

ここで、データ以外の部分を取り除き、並べ替えます。

class mutex : private noncopyable {
private:
#if defined(BOOST_HAS_WINTHREADS)
    void* m_mutex;
#elif defined(BOOST_HAS_PTHREADS)
    pthread_mutex_t m_mutex;
#elif defined(BOOST_HAS_MPTASKS)
    threads::mac::detail::scoped_critical_region m_mutex;
    threads::mac::detail::scoped_critical_region m_mutex_mutex;
#endif
};

そのnoncopyableため、システムミューテックスで発生しないオーバーヘッドはあまり見られません。

于 2011-07-25T13:10:27.307 に答える