2

一種の共有オブジェクトを作成する必要があります (何らかの理由で)。シングルスレッドの使用に限定されません。一般に、このような場合は連動操作が適しています ( Win32のInterlockedIncrementやなど)。InterlockedDecrement

オブジェクト参照カウントはどのシナリオでも正しく機能するはずですが、シングル スレッドでの使用に合わせて最適化したいと考えています。連動演算は、標準の算術演算よりもはるかに重いです。私の測定によると、インターロック操作 (完全なメモリ バリアを発行する) は、私の「典型的な」CPU で約 40 CPU サイクルかかりますが、標準の算術演算は (CPU キャッシュのおかげで) 測定精度を下回っています。

メモリ割り当てに関しても同様の手法があります。「TCMalloc」などのヒープ実装があります。これは、適切な同期オブジェクトとスレッドごとのキャッシュによって保護された集中メモリ パーティション メカニズムで構成されます。最も一般的なシナリオでは、インターロック操作をまったく含まないスレッドごとのキャッシュで割り当てられた/解放されたメモリに加えて、CPU キャッシュが高い確率で使用されます。

したがって、参照をサポートするオブジェクトに対して同様のことを行う可能性について考えました。これを達成する方法はありますか?生のアイデアも大歓迎です。

私のシナリオでは、実際のオブジェクトの破棄をしばらく遅らせても問題ありません。これによりパフォーマンスが向上します。

4

1 に答える 1

1

気にしない。私はこのベンチマークを実行しました:

#include<stdio.h>

#define SIZE 1000000

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}


void print_avg(const char *str, const int *diff, int size)
{
    int i;
    long sum = 0;
    int max = -1, min = 10000;
    for(i = 0; i < size; i++)
    {
    int t = diff[i];
    sum += t;
    if (t > max) max = t;
    if (t < min) min = t;
    }

    printf("%s average =%f clocks, max =%d, min =%d\n", str, (double)sum / size, max, min);
}



int main()
{
    unsigned long long a, b;
    int diff[SIZE];
    int value = 0;
    int i;


    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();

    __sync_fetch_and_add(&value, 2);
    b = rdtsc();

    diff[i] = (int)(b - a);
    }

    print_avg("Locked", diff, SIZE);

    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();
        value += 2;
    b = rdtsc();
    diff[i] = (int)(b - a);
    }
    print_avg("Not locked", diff, SIZE);

    return 0;
}

gcc -O2でコンパイルすると、次の結果が得られます。

Locked average =105.672402 clocks, max =38756, min =86  
Not locked average =80.540389 clocks, max =23433, min =73

何度か実行しましたが、毎回結果は非常に似ています。maxの大きな数字は無視してください。これは、プロセッサが割り込みなどを受け取るときです。これは、別の目的で作成したコードに由来するものであり、このテストに再利用しました。この小さな違いは、すべての最新のプロセッサー(InteliCoreおよびAMDAthlon64およびそれらの種類の世代)に適用されるはずです。

何らかの理由でコンパイラがInterlockedIncrementをインライン化しない場合を除いて、コードにifステートメントを追加すると少なくとも5サイクルかかる可能性が高いため、最大10サイクル節約できます。うまくいけば、参照カウンターをインクリメントおよびデクリメントする以外のことをしていることになります。

編集:メモリバリアを追加しても大きな違いはありません-約10サイクル。

確かに、2番目のループで10個の加算を追加すると、各ループに約5クロックサイクルが発生します(つまり、平均して1回の加算で0.5クロック)。一方、ロックされた加算では、1回の加算で約20クロックかかります。私の意見では、ifステートメントを追加する価値はまだありません。しかし、「if(nr_threads == 1)a = a + 1; else a = __sync_fetch_and_add(a、1);」を追加したい場合は、[または必要なことを行うために必要なことは何でも]私はあなたを止めるつもりはありません。ただし、アプリケーション全体のベンチマークを行い、1%以上の改善が見られることを確認してください。私はそれを疑っています。戻ってきて、違いを教えてください。Linuxカーネルの「ページテーブルエントリの割り当て解除」に追加したifステートメントにより、2〜5%遅くなったため、価値がありませんでした。しかし、コードでそれを見つけた場合は、それだけの価値があります。私のゲストになってください。私は経験から話している、

于 2012-12-30T00:16:03.410 に答える