4

私はマルチスレッドにまったく慣れていません。並列化の可能性がかなりあるシングルスレッドのデータ分析アプリを持っています。データセットは大きいですが、ハードディスクの読み取り/書き込みを飽和させることはありません。現在標準になっているスレッド化のサポートを利用して、高速化を図るべきです。

いくつかの調査の後、ディスクからデータを読み取って処理するにはプロデューサー コンシューマーが適切なアプローチであると判断し、プロデューサーがデータを置き、コンシューマーが取得する循環バッファーの一部となるオブジェクト プールの作成を開始しました。データ。クラスを書いているときに、データ メンバーのロックと解放の処理方法が細かすぎるように感じました。コードの半分がロックとロック解除を行っており、非常に多くの同期オブジェクトが浮かんでいるように感じます。

そこで、クラス宣言とサンプル関数を用意して、次の質問をします。これはきめが細かすぎますか? きめが細かすぎませんか?よく考えられていませんか?

struct PoolArray
{
public:
    Obj* arr;
    uint32 used;
    uint32 refs;
    std::mutex locker;
};

class SegmentedPool
{
public: /*Construction and destruction cut out*/
    void alloc(uint32 cellsNeeded, PoolPtr& ptr);
    void dealloc(PoolPtr& ptr);
    void clearAll();
private:
    void expand();

    //stores all the segments of the pool
    std::vector< PoolArray<Obj> > pools;
    ReadWriteLock poolLock;

    //stores pools that are empty
    std::queue< int > freePools;
    std::mutex freeLock;

    int currentPool;
    ReadWriteLock currentLock;
};

void SegmentedPool::dealloc(PoolPtr& ptr)
{
    //find and access the segment
    poolLock.lockForRead();
    PoolArray* temp = &(pools[ptr.getSeg()]);
    poolLock.unlockForRead();
    //reduce the count of references in the segment
    temp->locker.lock();
    --(temp->refs);
    //if the number of references is now zero then set the segment back to unused
    //and push it onto the queue of empty segments so that it can be reused
    if(temp->refs==0)
    {
        temp->used=0;
        freeLock.lock();
        freePools.push(ptr.getSeg());
        freeLock.unlock();
    }
    temp->locker.unlock();
    ptr.set(NULL,-1);
}

いくつかの説明: 最初の PoolPtr は、ポインターと、ポインターの元のプール内のセグメント番号を格納するオブジェクトのような愚かな小さなポインターです。

第二に、これはすべて「テンプレート化」されていますが、コードブロックの長さを減らすためにそれらの行を取り出しました

3 番目の ReadWriteLock は、mutex と条件変数のペアを使用してまとめたものです。

4

2 に答える 2

3

ロックはどんなに細かくても非効率的ですので、絶対に避けてください。

compare-swapキューとベクトルの両方は、プリミティブを使用してロックフリーで簡単に実装できます。

このトピックに関する論文はたくさんあります

空きキューをロックする:

無料のベクトルをロックします。

Straustrupの論文は、ロックフリーアロケータについても言及していますが、すぐにジャンプしないでください。最近では、標準のアロケータがかなり優れています。

UPD 独自のコンテナを作成する必要がない場合は、IntelのThreading Building Blocksライブラリを使用してください。これにより、スレッドセーフなベクターとキューの両方が提供されます。これらはロックフリーではありませんが、CPUキャッシュを効率的に使用するように最適化されています。

UPD に関してPoolArrayは、そこにもロックは必要ありません。c ++ 11を使用できる場合はstd::atomic、アトミックインクリメントとスワップに使用します。それ以外の場合は、コンパイラ組み込み(MSVCではInterLocked *関数、gcchttp://gcc.gnu.org/onlinedocs/gcc-4.1では_sync*)を使用します。 1 / gcc / Atomic-Builtins.html

于 2012-12-06T08:51:33.863 に答える
1

良いスタートです。必要なときにロックし、終了したらすぐに解放します。

あなたReadWriteLockはほとんどCCriticalSectionオブジェクトです-必要に応じて、代わりにそれを使用することでパフォーマンスが向上する場合があります。

私が言うことの1つはtemp->locker.lock();、プールのロックを解放する前に関数を呼び出すpoolLock.unlockForRead();ことです。そうしないと、同期制御下にないときにプールオブジェクトで操作を実行しています-その時点で別のスレッドによって使用されている可能性があります。マイナーなポイントですが、マルチスレッドでは、最終的につまずくのはマイナーなポイントです。

マルチスレッドへの適切なアプローチは、内部でロックとロック解除を行うオブジェクトまたは関数で制御されたリソースをラップすることです。これにより、データにアクセスしたい人は、どのロックをロックまたはロック解除するかを心配する必要がなくなります。そしていつそれをするか。例えば:

  ...
  if(temp->refs==0)
  {
    temp->used=0;
    freeLock.lock();
    freePools.push(ptr.getSeg());
    freeLock.unlock();
  }
  ...

だろう...

  ...
  if(temp->refs==0)
  {
    temp->used=0;
    addFreePool(ptr.getSeg());
  }
  ...

void SegmentedPool::addFreePool(unsigned int seg)
{
  freeLock.lock();
  freePools.push(seg);
  freeLock.unlock();
}

マルチスレッドのベンチマーク ツールもたくさんあります。さまざまな方法でリソースを制御し、ツールの 1 つを使用して実行し、パフォーマンスが問題になっていると思われる場合はボトルネックがどこにあるかを確認できます。

于 2012-12-06T09:12:38.577 に答える