34

NUMA の非対称メモリ アクセスの影響を測定しようとしましたが、失敗しました。

実験

Intel Xeon X5570 @ 2.93GHz、2 CPU、8 コアで実行。

コア 0 に固定されたスレッドで、numa_alloc_local を使用してコア 0 の NUMA ノードにサイズ 10,000,000 バイトの配列xを割り当てます。次に、配列xを 50 回繰り返し、配列内の各バイトを読み書きします。50 回の反復を実行するのにかかった時間を測定します。

次に、サーバーの他の各コアで新しいスレッドを固定し、配列x内のすべてのバイトの読み取りと書き込みを 50 回繰り返して経過時間を再度測定します。

キャッシュの影響を最小限に抑えるために、配列xは大きくなっています。キャッシュが役に立っている場合ではなく、ロードとストアのために CPU が RAM まで行かなければならない場合の速度を測定したいと考えています。

サーバーには 2 つの NUMA ノードがあるため、配列xが割り当てられている同じノードにアフィニティを持つコアは、読み取り/書き込み速度が速いと予想されます。私はそれを見ていません。

なんで?

おそらくNUMAは、他の場所で提案されているように、8〜12コアを超えるシステムにのみ関連していますか?

http://lse.sourceforge.net/numa/faq/

numest.cpp

#include <numa.h>
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <pthread.h>

void pin_to_core(size_t core)
{
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}

std::ostream& operator<<(std::ostream& os, const bitmask& bm)
{
    for(size_t i=0;i<bm.size;++i)
    {
        os << numa_bitmask_isbitset(&bm, i);
    }
    return os;
}

void* thread1(void** x, size_t core, size_t N, size_t M)
{
    pin_to_core(core);

    void* y = numa_alloc_local(N);

    boost::posix_time::ptime t1 = boost::posix_time::microsec_clock::universal_time();

    char c;
    for (size_t i(0);i<M;++i)
        for(size_t j(0);j<N;++j)
        {
            c = ((char*)y)[j];
            ((char*)y)[j] = c;
        }

    boost::posix_time::ptime t2 = boost::posix_time::microsec_clock::universal_time();

    std::cout << "Elapsed read/write by same thread that allocated on core " << core << ": " << (t2 - t1) << std::endl;

    *x = y;
}

void thread2(void* x, size_t core, size_t N, size_t M)
{
    pin_to_core(core);

    boost::posix_time::ptime t1 = boost::posix_time::microsec_clock::universal_time();

    char c;
    for (size_t i(0);i<M;++i)
        for(size_t j(0);j<N;++j)
        {
            c = ((char*)x)[j];
            ((char*)x)[j] = c;
        }

    boost::posix_time::ptime t2 = boost::posix_time::microsec_clock::universal_time();

    std::cout << "Elapsed read/write by thread on core " << core << ": " << (t2 - t1) << std::endl;
}

int main(int argc, const char **argv)
{
    int numcpus = numa_num_task_cpus();
    std::cout << "numa_available() " << numa_available() << std::endl;
    numa_set_localalloc();

    bitmask* bm = numa_bitmask_alloc(numcpus);
    for (int i=0;i<=numa_max_node();++i)
    {
        numa_node_to_cpus(i, bm);
        std::cout << "numa node " << i << " " << *bm << " " << numa_node_size(i, 0) << std::endl;
    }
    numa_bitmask_free(bm);

    void* x;
    size_t N(10000000);
    size_t M(50);

    boost::thread t1(boost::bind(&thread1, &x, 0, N, M));
    t1.join();

    for (size_t i(0);i<numcpus;++i)
    {
        boost::thread t2(boost::bind(&thread2, x, i, N, M));
        t2.join();
    }

    numa_free(x, N);

    return 0;
}

出力

g++ -o numatest -pthread -lboost_thread -lnuma -O0 numatest.cpp

./numatest

numa_available() 0                    <-- NUMA is available on this system
numa node 0 10101010 12884901888      <-- cores 0,2,4,6 are on NUMA node 0, which is about 12 Gb
numa node 1 01010101 12874584064      <-- cores 1,3,5,7 are on NUMA node 1, which is slightly smaller than node 0

Elapsed read/write by same thread that allocated on core 0: 00:00:01.767428
Elapsed read/write by thread on core 0: 00:00:01.760554
Elapsed read/write by thread on core 1: 00:00:01.719686
Elapsed read/write by thread on core 2: 00:00:01.708830
Elapsed read/write by thread on core 3: 00:00:01.691560
Elapsed read/write by thread on core 4: 00:00:01.686912
Elapsed read/write by thread on core 5: 00:00:01.691917
Elapsed read/write by thread on core 6: 00:00:01.686509
Elapsed read/write by thread on core 7: 00:00:01.689928

どのコアが読み取りと書き込みを行っているかに関係なく、配列x に対して読み取りと書き込みを 50 回繰り返すには、約 1.7 秒かかります。

アップデート:

私の CPU のキャッシュ サイズは 8Mb なので、おそらく 10Mb の配列xではキャッシュ効果を排除するには十分な大きさではありません。100Mb 配列xを試し、最も内側のループ内で __sync_synchronize() を使用して完全なメモリ フェンスを発行しようとしました。それでも、NUMA ノード間の非対称性は明らかになりません。

更新 2:

__sync_fetch_and_add() を使用して、配列xの読み取りと書き込みを試みました。まだ何もありません。

4

6 に答える 6

21

最初に指摘したいのは、各ノードにどのコアがあるかを再確認したい場合があるということです。コアとノードがそのようにインターリーブされたことを思い出しません。また、HT のために 16 スレッドが必要です。(無効にしない限り)

別物:

ソケット 1366 Xeon マシンはわずかに NUMA です。そのため、違いがわかりにくいでしょう。NUMA 効果は、4P Opteron でより顕著になります。

あなたのようなシステムでは、ノードからノードへの帯域幅は実際には CPU からメモリへの帯域幅よりも高速です。アクセス パターンは完全にシーケンシャルであるため、データがローカルかどうかに関係なく、帯域幅をすべて利用できます。測定するより良いことは、待ち時間です。順次ストリーミングするのではなく、1 GB のブロックにランダム アクセスしてみてください。

最後のこと:

コンパイラがどれだけ積極的に最適化するかに応じて、何もしないため、ループが最適化される可能性があります。

c = ((char*)x)[j];
((char*)x)[j] = c;

このようなものは、コンパイラによって削除されないことを保証します:

((char*)x)[j] += 1;
于 2011-08-31T18:21:13.263 に答える
15

あはは!ミスティアル そうですね!どういうわけか、ハードウェアのプリフェッチが読み取り/書き込みを最適化しています。

それがキャッシュの最適化である場合、メモリバリアを強制すると最適化が無効になります。

c = __sync_fetch_and_add(((char*)x) + j, 1);

しかし、それは何の違いもありません。違いを生むのは、プリフェッチの最適化を無効にするために、イテレータ インデックスに素数 1009 を掛けることです。

*(((char*)x) + ((j * 1009) % N)) += 1;

この変更により、NUMA の非対称性が明らかになります。

numa_available() 0
numa node 0 10101010 12884901888
numa node 1 01010101 12874584064
Elapsed read/write by same thread that allocated on core 0: 00:00:00.961725
Elapsed read/write by thread on core 0: 00:00:00.942300
Elapsed read/write by thread on core 1: 00:00:01.216286
Elapsed read/write by thread on core 2: 00:00:00.909353
Elapsed read/write by thread on core 3: 00:00:01.218935
Elapsed read/write by thread on core 4: 00:00:00.898107
Elapsed read/write by thread on core 5: 00:00:01.211413
Elapsed read/write by thread on core 6: 00:00:00.898021
Elapsed read/write by thread on core 7: 00:00:01.207114

少なくとも私はそれが起こっていると思います。

ありがとうミスティカル!

編集: 結論 ~133%

NUMA のパフォーマンス特性の大まかなアイデアを得るためにこの投稿をちらりと見ている人のために、ここに私のテストによる結論を示します。

非ローカル NUMA ノードへのメモリ アクセスのレイテンシは、ローカル ノードへのメモリ アクセスの約 1.33 倍です。

于 2011-08-31T19:10:09.047 に答える
10

このベンチマーク コードをありがとう。あなたの「修正済み」バージョンを純粋な C + OpenMP に変更し、メモリ システムが競合下でどのように動作するかについていくつかのテストを追加しました。新しいコードはここにあります

以下は、Quad Opteron からのサンプル結果の一部です。

num cpus: 32
numa available: 0
numa node 0 10001000100010000000000000000000 - 15.9904 GiB
numa node 1 00000000000000001000100010001000 - 16 GiB
numa node 2 00010001000100010000000000000000 - 16 GiB
numa node 3 00000000000000000001000100010001 - 16 GiB
numa node 4 00100010001000100000000000000000 - 16 GiB
numa node 5 00000000000000000010001000100010 - 16 GiB
numa node 6 01000100010001000000000000000000 - 16 GiB
numa node 7 00000000000000000100010001000100 - 16 GiB

sequential core 0 -> core 0 : BW 4189.87 MB/s
sequential core 1 -> core 0 : BW 2409.1 MB/s
sequential core 2 -> core 0 : BW 2495.61 MB/s
sequential core 3 -> core 0 : BW 2474.62 MB/s
sequential core 4 -> core 0 : BW 4244.45 MB/s
sequential core 5 -> core 0 : BW 2378.34 MB/s
sequential core 6 -> core 0 : BW 2442.93 MB/s
sequential core 7 -> core 0 : BW 2468.61 MB/s
sequential core 8 -> core 0 : BW 4220.48 MB/s
sequential core 9 -> core 0 : BW 2442.88 MB/s
sequential core 10 -> core 0 : BW 2388.11 MB/s
sequential core 11 -> core 0 : BW 2481.87 MB/s
sequential core 12 -> core 0 : BW 4273.42 MB/s
sequential core 13 -> core 0 : BW 2381.28 MB/s
sequential core 14 -> core 0 : BW 2449.87 MB/s
sequential core 15 -> core 0 : BW 2485.48 MB/s
sequential core 16 -> core 0 : BW 2938.08 MB/s
sequential core 17 -> core 0 : BW 2082.12 MB/s
sequential core 18 -> core 0 : BW 2041.84 MB/s
sequential core 19 -> core 0 : BW 2060.47 MB/s
sequential core 20 -> core 0 : BW 2944.13 MB/s
sequential core 21 -> core 0 : BW 2111.06 MB/s
sequential core 22 -> core 0 : BW 2063.37 MB/s
sequential core 23 -> core 0 : BW 2082.75 MB/s
sequential core 24 -> core 0 : BW 2958.05 MB/s
sequential core 25 -> core 0 : BW 2091.85 MB/s
sequential core 26 -> core 0 : BW 2098.73 MB/s
sequential core 27 -> core 0 : BW 2083.7 MB/s
sequential core 28 -> core 0 : BW 2934.43 MB/s
sequential core 29 -> core 0 : BW 2048.68 MB/s
sequential core 30 -> core 0 : BW 2087.6 MB/s
sequential core 31 -> core 0 : BW 2014.68 MB/s

all-contention core 0 -> core 0 : BW 1081.85 MB/s
all-contention core 1 -> core 0 : BW 299.177 MB/s
all-contention core 2 -> core 0 : BW 298.853 MB/s
all-contention core 3 -> core 0 : BW 263.735 MB/s
all-contention core 4 -> core 0 : BW 1081.93 MB/s
all-contention core 5 -> core 0 : BW 299.177 MB/s
all-contention core 6 -> core 0 : BW 299.63 MB/s
all-contention core 7 -> core 0 : BW 263.795 MB/s
all-contention core 8 -> core 0 : BW 1081.98 MB/s
all-contention core 9 -> core 0 : BW 299.177 MB/s
all-contention core 10 -> core 0 : BW 300.149 MB/s
all-contention core 11 -> core 0 : BW 262.905 MB/s
all-contention core 12 -> core 0 : BW 1081.89 MB/s
all-contention core 13 -> core 0 : BW 299.173 MB/s
all-contention core 14 -> core 0 : BW 299.025 MB/s
all-contention core 15 -> core 0 : BW 263.865 MB/s
all-contention core 16 -> core 0 : BW 432.156 MB/s
all-contention core 17 -> core 0 : BW 233.12 MB/s
all-contention core 18 -> core 0 : BW 232.889 MB/s
all-contention core 19 -> core 0 : BW 202.48 MB/s
all-contention core 20 -> core 0 : BW 434.299 MB/s
all-contention core 21 -> core 0 : BW 233.274 MB/s
all-contention core 22 -> core 0 : BW 233.144 MB/s
all-contention core 23 -> core 0 : BW 202.505 MB/s
all-contention core 24 -> core 0 : BW 434.295 MB/s
all-contention core 25 -> core 0 : BW 233.274 MB/s
all-contention core 26 -> core 0 : BW 233.169 MB/s
all-contention core 27 -> core 0 : BW 202.49 MB/s
all-contention core 28 -> core 0 : BW 434.295 MB/s
all-contention core 29 -> core 0 : BW 233.309 MB/s
all-contention core 30 -> core 0 : BW 233.169 MB/s
all-contention core 31 -> core 0 : BW 202.526 MB/s

two-contention core 0 -> core 0 : BW 3306.11 MB/s
two-contention core 1 -> core 0 : BW 2199.7 MB/s

two-contention core 0 -> core 0 : BW 3286.21 MB/s
two-contention core 2 -> core 0 : BW 2220.73 MB/s

two-contention core 0 -> core 0 : BW 3302.24 MB/s
two-contention core 3 -> core 0 : BW 2182.81 MB/s

two-contention core 0 -> core 0 : BW 3605.88 MB/s
two-contention core 4 -> core 0 : BW 3605.88 MB/s

two-contention core 0 -> core 0 : BW 3297.08 MB/s
two-contention core 5 -> core 0 : BW 2217.82 MB/s

two-contention core 0 -> core 0 : BW 3312.69 MB/s
two-contention core 6 -> core 0 : BW 2227.04 MB/s

two-contention core 0 -> core 0 : BW 3287.93 MB/s
two-contention core 7 -> core 0 : BW 2209.48 MB/s

two-contention core 0 -> core 0 : BW 3660.05 MB/s
two-contention core 8 -> core 0 : BW 3660.05 MB/s

two-contention core 0 -> core 0 : BW 3339.63 MB/s
two-contention core 9 -> core 0 : BW 2223.84 MB/s

two-contention core 0 -> core 0 : BW 3303.77 MB/s
two-contention core 10 -> core 0 : BW 2197.99 MB/s

two-contention core 0 -> core 0 : BW 3323.19 MB/s
two-contention core 11 -> core 0 : BW 2196.08 MB/s

two-contention core 0 -> core 0 : BW 3582.23 MB/s
two-contention core 12 -> core 0 : BW 3582.22 MB/s

two-contention core 0 -> core 0 : BW 3324.9 MB/s
two-contention core 13 -> core 0 : BW 2250.74 MB/s

two-contention core 0 -> core 0 : BW 3305.66 MB/s
two-contention core 14 -> core 0 : BW 2209.5 MB/s

two-contention core 0 -> core 0 : BW 3303.52 MB/s
two-contention core 15 -> core 0 : BW 2182.43 MB/s

two-contention core 0 -> core 0 : BW 3352.74 MB/s
two-contention core 16 -> core 0 : BW 2607.73 MB/s

two-contention core 0 -> core 0 : BW 3092.65 MB/s
two-contention core 17 -> core 0 : BW 1911.98 MB/s

two-contention core 0 -> core 0 : BW 3025.91 MB/s
two-contention core 18 -> core 0 : BW 1918.06 MB/s

two-contention core 0 -> core 0 : BW 3257.56 MB/s
two-contention core 19 -> core 0 : BW 1885.03 MB/s

two-contention core 0 -> core 0 : BW 3339.64 MB/s
two-contention core 20 -> core 0 : BW 2603.06 MB/s

two-contention core 0 -> core 0 : BW 3119.29 MB/s
two-contention core 21 -> core 0 : BW 1918.6 MB/s

two-contention core 0 -> core 0 : BW 3054.14 MB/s
two-contention core 22 -> core 0 : BW 1910.61 MB/s

two-contention core 0 -> core 0 : BW 3214.44 MB/s
two-contention core 23 -> core 0 : BW 1881.69 MB/s

two-contention core 0 -> core 0 : BW 3332.3 MB/s
two-contention core 24 -> core 0 : BW 2611.8 MB/s

two-contention core 0 -> core 0 : BW 3111.94 MB/s
two-contention core 25 -> core 0 : BW 1922.11 MB/s

two-contention core 0 -> core 0 : BW 3049.02 MB/s
two-contention core 26 -> core 0 : BW 1912.85 MB/s

two-contention core 0 -> core 0 : BW 3251.88 MB/s
two-contention core 27 -> core 0 : BW 1881.82 MB/s

two-contention core 0 -> core 0 : BW 3345.6 MB/s
two-contention core 28 -> core 0 : BW 2598.82 MB/s

two-contention core 0 -> core 0 : BW 3109.04 MB/s
two-contention core 29 -> core 0 : BW 1923.81 MB/s

two-contention core 0 -> core 0 : BW 3062.94 MB/s
two-contention core 30 -> core 0 : BW 1921.3 MB/s

two-contention core 0 -> core 0 : BW 3220.8 MB/s
two-contention core 31 -> core 0 : BW 1901.76 MB/s

誰かがさらに改善した場合は、それらについて聞いてうれしいです. たとえば、これらは明らかに、実際の単位での完全な帯域幅の測定値ではありません (おそらく定数であることが望ましい) 整数係数によってずれている可能性があります。

于 2012-10-22T20:00:28.147 に答える
6

いくつかのコメント:

  • システムの NUMA 構造を確認するには (Linux の場合)、hwlocライブラリのlstopoユーティリティを使用してグラフィカルな概要を取得できます。特に、どのコア番号がどの NUMA ノード (プロセッサ ソケット) のメンバーであるかがわかります。
  • char最大 RAM スループットを測定するには、おそらく理想的なデータ型ではありません。32ビットまたは64ビットのデータ型を使用すると、同じ数のCPUサイクルでより多くのデータを取得できると思います。
  • より一般的には、測定が CPU 速度によって制限されているのではなく、RAM 速度によって制限されていることも確認する必要があります。たとえば、ramspeedユーティリティは、ソース コードで内側のループをある程度明示的に展開します。

    for(i = 0; i < blk/sizeof(UTL); i += 32) {
        b[i] = a[i];        b[i+1] = a[i+1];
        ...
        b[i+30] = a[i+30];  b[i+31] = a[i+31];
    }
    

    編集:サポートされているアーキテクチャramsmpでは、実際にはこれらのループに「手書きの」アセンブリコードを使用しています

  • L1/L2/L3 キャッシュ効果: ブロック サイズの関数として GByte/s で帯域幅を測定することは有益です。データの読み取り元 (キャッシュまたはメイン メモリ) に対応するブロック サイズを大きくすると、およそ 4 つの異なる速度が表示されるはずです。お使いのプロセッサには8 MBのレベル 3 (?) キャッシュがあるようです。そのため、1,000 万バイトのほとんどが L3 キャッシュ (1 つのプロセッサのすべてのコアで共有される) に留まる可能性があります。

  • メモリ チャネル: プロセッサには3 つのメモリ チャネルがあります。メモリ バンクがすべて利用できるようにインストールされている場合 (マザーボードのマニュアルなどを参照)、同時に複数のスレッドを実行したい場合があります。1 つのスレッドのみで読み取る場合、漸近帯域幅は 1 つのメモリ モジュールの帯域幅 (例: DDR-1600 の場合は 12.8 GByte/s) に近く、複数のスレッドを実行する場合、漸近帯域幅は数値に近いという効果が見られました。メモリ チャネルの数は、単一のメモリ モジュールの帯域幅に掛けます。

于 2012-12-14T17:39:58.680 に答える
5

numactl を使用して、プロセスを実行するノードとメモリを割り当てる場所を選択することもできます。

numactl --cpubind=0 --membind=1 <process>

これをLMbenchと組み合わせて使用​​して、メモリ レイテンシの数値を取得します。

numactl --cpubind=0 --membind=0  ./lat_mem_rd -t 512
numactl --cpubind=0 --membind=1  ./lat_mem_rd -t 512
于 2013-04-18T10:56:09.643 に答える
3

他の誰かがこのテストを試してみたい場合は、修正された動作中のプログラムを次に示します。他のハードウェアからの結果を見てみたいです。これは、Linux 2.6.34-12-desktop、GCC 4.5.0、Boost 1.47 を搭載したマシンで動作します。

g++ -o numatest -pthread -lboost_thread -lnuma -O0 numatest.cpp

numest.cpp

#include <numa.h>
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <pthread.h>

void pin_to_core(size_t core)
{
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}

std::ostream& operator<<(std::ostream& os, const bitmask& bm)
{
    for(size_t i=0;i<bm.size;++i)
    {
        os << numa_bitmask_isbitset(&bm, i);
    }
    return os;
}

void* thread1(void** x, size_t core, size_t N, size_t M)
{
    pin_to_core(core);

    void* y = numa_alloc_local(N);

    boost::posix_time::ptime t1 = boost::posix_time::microsec_clock::universal_time();

    char c;
    for (size_t i(0);i<M;++i)
        for(size_t j(0);j<N;++j)
        {
            *(((char*)y) + ((j * 1009) % N)) += 1;
        }

    boost::posix_time::ptime t2 = boost::posix_time::microsec_clock::universal_time();

    std::cout << "Elapsed read/write by same thread that allocated on core " << core << ": " << (t2 - t1) << std::endl;

    *x = y;
}

void thread2(void* x, size_t core, size_t N, size_t M)
{
    pin_to_core(core);

    boost::posix_time::ptime t1 = boost::posix_time::microsec_clock::universal_time();

    char c;
    for (size_t i(0);i<M;++i)
        for(size_t j(0);j<N;++j)
        {
            *(((char*)x) + ((j * 1009) % N)) += 1;
        }

    boost::posix_time::ptime t2 = boost::posix_time::microsec_clock::universal_time();

    std::cout << "Elapsed read/write by thread on core " << core << ": " << (t2 - t1) << std::endl;
}

int main(int argc, const char **argv)
{
    int numcpus = numa_num_task_cpus();
    std::cout << "numa_available() " << numa_available() << std::endl;
    numa_set_localalloc();

    bitmask* bm = numa_bitmask_alloc(numcpus);
    for (int i=0;i<=numa_max_node();++i)
    {
        numa_node_to_cpus(i, bm);
        std::cout << "numa node " << i << " " << *bm << " " << numa_node_size(i, 0) << std::endl;
    }
    numa_bitmask_free(bm);

    void* x;
    size_t N(10000000);
    size_t M(5);

    boost::thread t1(boost::bind(&thread1, &x, 0, N, M));
    t1.join();

    for (size_t i(0);i<numcpus;++i)
    {
        boost::thread t2(boost::bind(&thread2, x, i, N, M));
        t2.join();
    }

    numa_free(x, N);

    return 0;
}
于 2011-08-31T20:27:29.353 に答える