23

現在、TBB フロー グラフを使用しています。このグラフでは、a) 並列フィルターが配列を (オフセットと並行して) 処理し、処理された結果を中間ベクトル (ヒープに割り当てられます。ほとんどの場合、ベクトルは 8MB まで増加します) に入れます。次に、これらのベクトルはノードに渡され、ノードはその特性 (a) で決定) に基づいてこれらの結果を後処理します。リソースが同期されているため、各特性に対してこのようなノードは 1 つしか存在できません。私たちが作成したプロトタイプは、UMA アーキテクチャ (単一 CPU の Ivy Bridge および Sandy Bridge アーキテクチャでテスト済み) でうまく動作します。ただし、アプリケーションは NUMA アーキテクチャ (4 CPU Nehalem-EX) ではスケーリングしません。問題をメモリ割り当てに突き止め、ヒープからメモリを割り当てるだけの並列パイプラインを持つ最小限の例を作成しました (8MB チャンクの malloc を介して、次に、8MB 領域を memset します。最初のプロトタイプが行うことと同様) メモリの特定の量まで。私たちの調査結果は次のとおりです。

  • UMA アーキテクチャでは、アプリケーションは、パイプラインで使用されるスレッドの数 (task_scheduler_init で設定) に比例してスケールアップします。

  • NUMA アーキテクチャでは、(numactl を使用して) アプリケーションを 1 つのソケットに固定すると、同じ線形スケールアップが見られます。

  • 複数のソケットを使用する NUMA アーキテクチャーでは、アプリケーションの実行時間はソケットの数に応じて増加します (負の線形スケール - 「アップ」)。

私たちにとって、これはヒープの競合のようなにおいがします。これまでに試したことは、glibc アロケーターを Intel の TBB スケーラブル アロケーターに置き換えることです。ただし、単一ソケットでの初期パフォーマンスは glibc を使用するよりも悪く、複数ソケットでのパフォーマンスは悪化していませんが、改善もされていません。 tcmalloc、買いだめアロケータ、および TBB のキャッシュ アライメント アロケータを使用して同じ効果を得ました。

問題は、誰かが同様の問題を経験したかどうかです。パイプラインが実行された後でもヒープに割り当てられたベクトルを保持したいので、スタック割り当てはオプションではありません。複数のスレッドから NUMA アーキテクチャで、1 つのヒープに MB 単位のサイズのメモリ領域を効率的に割り当てるにはどうすればよいですか? メモリを事前に割り当ててアプリケーション内で管理するのではなく、動的割り当てアプローチを維持したいと考えています。

numactl を使用したさまざまな実行のパフォーマンス統計を添付しました。Interleaving/localalloc はまったく効果がありません (QPI バスはボトルネックではありません。PCM を使用した場合、QPI リンクの負荷が 1% であることを確認しました)。また、glibc、tbbmalloc、および tcmalloc の結果を示すグラフも追加しました。

perf stat bin/prototype 598.867

「bin/prototype」のパフォーマンス カウンター統計:

  12965,118733 task-clock                #    7,779 CPUs utilized          
        10.973 context-switches          #    0,846 K/sec                  
         1.045 CPU-migrations            #    0,081 K/sec                  
       284.210 page-faults               #    0,022 M/sec                  
17.266.521.878 cycles                    #    1,332 GHz                     [82,84%]
15.286.104.871 stalled-cycles-frontend   #   88,53% frontend cycles idle    [82,84%]
10.719.958.132 stalled-cycles-backend    #   62,09% backend  cycles idle    [67,65%]
 3.744.397.009 instructions              #    0,22  insns per cycle        
                                         #    4,08  stalled cycles per insn [84,40%]
   745.386.453 branches                  #   57,492 M/sec                   [83,50%]
    26.058.804 branch-misses             #    3,50% of all branches         [83,33%]

   1,666595682 seconds time elapsed

perf stat numactl --cpunodebind=0 ビン/プロトタイプ 272.614

「numactl --cpunodebind=0 bin/prototype」のパフォーマンス カウンター統計:

   3887,450198 task-clock                #    3,345 CPUs utilized          
         2.360 context-switches          #    0,607 K/sec                  
           208 CPU-migrations            #    0,054 K/sec                  
       282.794 page-faults               #    0,073 M/sec                  
 8.472.475.622 cycles                    #    2,179 GHz                     [83,66%]
 7.405.805.964 stalled-cycles-frontend   #   87,41% frontend cycles idle    [83,80%]
 6.380.684.207 stalled-cycles-backend    #   75,31% backend  cycles idle    [66,90%]
 2.170.702.546 instructions              #    0,26  insns per cycle        
                                         #    3,41  stalled cycles per insn [85,07%]
   430.561.957 branches                  #  110,757 M/sec                   [82,72%]
    16.758.653 branch-misses             #    3,89% of all branches         [83,06%]

   1,162185180 seconds time elapsed

perf stat numactl --cpunodebind=0-1 ビン/プロトタイプ 356.726

「numactl --cpunodebind=0-1 bin/prototype」のパフォーマンス カウンター統計:

   6127,077466 task-clock                #    4,648 CPUs utilized          
         4.926 context-switches          #    0,804 K/sec                  
           469 CPU-migrations            #    0,077 K/sec                  
       283.291 page-faults               #    0,046 M/sec                  
10.217.787.787 cycles                    #    1,668 GHz                     [82,26%]
 8.944.310.671 stalled-cycles-frontend   #   87,54% frontend cycles idle    [82,54%]
 7.077.541.651 stalled-cycles-backend    #   69,27% backend  cycles idle    [68,59%]
 2.394.846.569 instructions              #    0,23  insns per cycle        
                                         #    3,73  stalled cycles per insn [84,96%]
   471.191.796 branches                  #   76,903 M/sec                   [83,73%]
    19.007.439 branch-misses             #    4,03% of all branches         [83,03%]

   1,318087487 seconds time elapsed

perf stat numactl --cpunodebind=0-2 ビン/プロトタイプ 472.794

「numactl --cpunodebind=0-2 bin/prototype」のパフォーマンス カウンター統計:

   9671,244269 task-clock                #    6,490 CPUs utilized          
         7.698 context-switches          #    0,796 K/sec                  
           716 CPU-migrations            #    0,074 K/sec                  
       283.933 page-faults               #    0,029 M/sec                  
14.050.655.421 cycles                    #    1,453 GHz                     [83,16%]
12.498.787.039 stalled-cycles-frontend   #   88,96% frontend cycles idle    [83,08%]
 9.386.588.858 stalled-cycles-backend    #   66,81% backend  cycles idle    [66,25%]
 2.834.408.038 instructions              #    0,20  insns per cycle        
                                         #    4,41  stalled cycles per insn [83,44%]
   570.440.458 branches                  #   58,983 M/sec                   [83,72%]
    22.158.938 branch-misses             #    3,88% of all branches         [83,92%]

   1,490160954 seconds time elapsed

最小限の例: g++-4.7 std=c++11 -O3 -march=native; でコンパイル。numactl --cpunodebind=0 ... numactl --cpunodebind=0-3 で実行 - CPU バインドでは、次の結果が得られます: 1 CPU (速度 x)、2 CPU (速度 ~ x/2)、3 CPU (速度~ x/3) [速度=高いほど良い]. したがって、CPU の数が増えるとパフォーマンスが低下することがわかります。メモリ バインディング、インターリーブ (--interleave=all)、および --localalloc は、ここでは効果がありません (すべての QPI リンクを監視し、リンク負荷は各リンクで 1% 未満でした)。

#include <tbb/pipeline.h>
#include <tbb/task_scheduler_init.h>
#include <chrono>
#include <stdint.h>
#include <iostream>
#include <fcntl.h>
#include <sstream>
#include <sys/mman.h>
#include <tbb/scalable_allocator.h>
#include <tuple>

namespace {
// 8 MB
size_t chunkSize = 8 * 1024 * 1024;
// Number of threads (0 = automatic)
uint64_t threads=0;
}

using namespace std;
typedef chrono::duration<double, milli> milliseconds;

int main(int /* argc */, char** /* argv */)
{
   chrono::time_point<chrono::high_resolution_clock> startLoadTime = chrono::high_resolution_clock::now();
   tbb::task_scheduler_init init(threads==0?tbb::task_scheduler_init::automatic:threads);
   const uint64_t chunks=128;
   uint64_t nextChunk=0;
   tbb::parallel_pipeline(128,tbb::make_filter<void,uint64_t>(
         tbb::filter::serial,[&](tbb::flow_control& fc)->uint64_t
   {
      uint64_t chunk=nextChunk++;
      if(chunk==chunks)
         fc.stop();

      return chunk;
   }) & tbb::make_filter<uint64_t,void>(
         tbb::filter::parallel,[&](uint64_t /* item */)->void
   {
        void* buffer=scalable_malloc(chunkSize);
        memset(buffer,0,chunkSize);
   }));

   chrono::time_point<chrono::high_resolution_clock> endLoadTime = chrono::high_resolution_clock::now();
   milliseconds loadTime = endLoadTime - startLoadTime;
   cout << loadTime.count()<<endl;
}

Intel TBB フォーラムでのディスカッション: http://software.intel.com/en-us/forums/topic/346334

4

2 に答える 2

5

説明されている問題に対する短い更新と部分的な回答: mallocorの呼び出しscalable_mallocはボトルネックではありません。ボトルネックはmemset、割り当てられたメモリを ting することによって引き起こされるページ フォールトです。mallocglibcと Intel の TBB などの他のスケーラブルなアロケーターとの間に違いはありません: 特定のしきい値 (何もない場合は通常 1MB ; で定義できますscalable_malloc) より大きい割り当ての場合freemadvise) メモリは無名の mmap によって割り当てられます。最初は、マップのすべてのページが、pre-0ed で読み取り専用のカーネル内部ページを指しています。メモリを memset すると、例外 (カーネル ページが読み取り専用であることに注意してください) とページ フォールトがトリガーされます。この時点で新しいページが 0 になります。小さなページは 4KB であるため、これは割り当てて書き込む 8MB のバッファに対して 2048 回発生します。私が測定したところ、これらのページ フォールトは、シングル ソケット マシンではそれほど高価ではありませんが、複数の CPU を搭載した NUMA マシンではますます高価になります。

私がこれまでに思いついた解決策:

  • 巨大なページを使用する: 問題を解決するのに役立ちますが、遅らせるだけです

  • 事前に割り当てられ、事前に障害が発生した (memsetまたはmmap+のいずれかMAP_POPULATE) メモリ領域 (メモリ プール) を使用し、そこから割り当てます。

  • Linux カーネルでこのスケーラビリティの問題に対処する

于 2012-12-18T13:10:05.270 に答える
2

2番目の更新(質問を閉じる):

サンプル アプリケーションを 3.10 カーネルで再度プロファイリングしました。

16 GB のデータの並列割り当てと memsetting の結果:

小さなページ:

  • 1 ソケット: 3112.29 ms
  • 2 ソケット: 2965.32 ms
  • 3 ソケット: 3000.72 ms
  • 4 ソケット: 3211.54 ms

巨大なページ:

  • 1 ソケット: 3086.77 ms
  • 2 ソケット: 1568.43 ms
  • 3 ソケット: 1084.45 ミリ秒
  • 4 ソケット: 852.697 ミリ秒

スケーラブルな割り当ての問題は現在修正されているようです - 少なくともヒュージページについては。

于 2013-07-05T10:57:02.677 に答える