4

4K(またはそのような値)のチャンクに分割された共有tempfileリソースがあります。ファイル内の各4Kは、ゼロから始まるインデックスで表されます。この共有リソースでは、使用中の4Kチャンクインデックスを追跡し、使用されていない最も低いインデックスの4Kチャンクを常に返します。すべてが使用されている場合は、-1を返します。

インデックス用のこのResourceSetクラスには、パブリックの取得および解放メソッドがあり、どちらも同期ロックを使用します。このロックの期間は、4つの乱数を生成する場合とほぼ同じです(高価、CPU単位)。

したがって、次のコードからわかるように、AtomicIntegerの「カウントセマフォ」を使用して、acquire()で多数のスレッドが同時にクリティカルセクションに入るのを防ぎ、次の場合に-1(現在は使用できません)を返します。スレッドが多すぎます。

現在、タイトなCASループに定数100を使用して、取得時にアトミック整数をインクリメントしようとしています。また、クリティカルセクションに入ることができるスレッドの最大数に定数10を使用しています。これは、競合を引き起こすのに十分な長さです。 。私の質問は、これらの4Kチャンクにアクセスしようとする複数のスレッドを持つ中程度から高負荷のサーブレットエンジンの場合、これらの定数はどうあるべきかということです。

public class ResourceSet {

    // ??? what should this be
    // maximum number of attempts to try to increment with CAS on acquire
    private static final int    CAS_MAX_ATTEMPTS = 50;

    // ??? what should this be
    // maximum number of threads contending for lock before returning -1 on acquire
    private static final int    CONTENTION_MAX = 10;

    private AtomicInteger        latch = new AtomicInteger(0);

    ... member variables to track free resources

    private boolean aquireLatchForAquire ()
    {
        for (int i = 0; i < CAS_MAX_ATTEMPTS; i++) {
            int val = latch.get();
            if (val == -1)
                throw new AssertionError("bug in ResourceSet");        // this means more threads than can exist on any system, so its a bug!
            if (!latch.compareAndSet(val, val+1))
                continue;
            if (val < 0 || val >= CONTENTION_MAX) {
                latch.decrementAndGet();
                // added to fix BUG that comment pointed out, thanks!
                return false;
            }
        }
        return false;
    }

    private void aquireLatchForRelease ()
    {
        do {
            int val = latch.get();
            if (val == -1)
                throw new AssertionError("bug in ResourceSet");    // this means more threads than can exist on any system, so its a bug!
            if (latch.compareAndSet(val, val+1))
                return;
        } while (true);
    }

    public ResourceSet (int totalResources)
    {
        ... initialize
    }

    public int acquire (ResourceTracker owned)
    {        
        if (!aquireLatchForAquire())
            return -1;

        try {
            synchronized (this) {
                ... algorithm to compute minimum free resoource or return -1 if all in use
                return resourceindex;
            }
        } finally {
            latch.decrementAndGet();
        }
    }

    public boolean release (ResourceIter iter)
    {
        aquireLatchForRelease();
        try {
            synchronized (this) {
                ... iterate and release all resources
            }
        } finally {
            latch.decrementAndGet();
        }
    }
}
4

3 に答える 3

1

優れたパフォーマンスのスピンロックを作成することは、実際にはかなり複雑であり、メモリバリアを十分に理解する必要があります。定数を選択するだけでは、それを削減することはできず、間違いなく移植性がありません。グーグルのgperftoolsにはあなたが見ることができるがありますが、おそらくあなたが必要とするものよりはるかに複雑です。

ロックでの競合を本当に減らしたい場合は、よりきめ細かく楽観的なスキームの使用を検討することをお勧めします。簡単な方法は、チャンクをn個のグループに分割し、各グループにロックを関連付けることです(ストリッピングとも呼ばれます)。これは、競合を減らしてスループットを向上させるのに役立ちますが、遅延を減らすのには役立ちません。また、AtomicBooleanを各チャンクとCASに関連付けて取得することもできます(失敗した場合は再試行してください)。ロックフリーアルゴリズムを扱うときは注意が必要です。正しく理解するのは難しい傾向があるからです。正しく理解すれば、チャンクを取得するまでの待ち時間を大幅に短縮できます。

チャンク選択アルゴリズムがどのように見えるかを知らずに、よりきめ細かいアプローチを提案することは難しいことに注意してください。また、あなたは本当にパフォーマンスの問題を抱えていると思います(プロファイルされており、すべてです)。

私がそれに取り組んでいる間、あなたのスピンロックの実装には欠陥があります。メモリバリアをスパムしているため、CASを直接スピンしないでください。これは、(雷鳴の群れの問題に関連する)深刻な量の競合があると、信じられないほど遅くなります。最低限、CASの前に変数の可用性を最初にチェックすることです(バリアなしの読み取りで実行できる場合は単純です)。さらに良いのは、すべてのスレッドが同じ値で回転しないようにすることです。これにより、関連するキャッシュラインがコア間でピンポンするのを防ぐことができます。

Javaのアトミック操作に関連付けられているメモリバリアのタイプがわからないため、上記の提案は最適または正しくない可能性があることに注意してください。

最後に、Art Of Multiprocessor Programmingは、私がこの回答で吐き出しているすべてのナンセンスをよりよく理解するために読むのが楽しい本です。

于 2012-05-11T01:59:09.310 に答える
0

このシナリオで独自のLockクラスを作成する必要があるかどうかはわかりません。JDKがReentrantLockを提供したため、ロック取得時にCAS命令も利用します。個人のロッククラスと比較すると、パフォーマンスはかなり良いはずです。

于 2012-05-11T00:15:50.980 に答える
0

スレッドをボークさせたい場合は、Semaphoreのメソッドを使用できます。 tryAcquireno resource available

synchronized私は、キーワードをaに置き換えて、そのメソッドReentrantLockを使用するだけです。tryLock()スレッドを少し待たせたい場合はtryLock(timeout)、同じクラスで使用できます。どちらを選択し、タイムアウトにどの値を使用するかは、パフォーマンステストによって決定する必要があります。

明示的なゲートを作成することは、あなたがしているように見えるので、私には不要のようです。私はそれが決して役に立たないと言っているわけではありませんが、IMOは実際にパフォーマンスを損なう可能性が高く、確かに追加の複雑さです。したがって、このあたりで(行ったテストに基づいて)パフォーマンスの問題があり、この種のゲーティングが役立つことがわかった場合を除いて、最も単純な実装を使用することをお勧めします。

于 2012-05-11T02:09:32.527 に答える