1

同じ機能をプログラムするには 2 つの方法があります。

方法 1:

doTheWork(int action)
{
    for(int i = 0 i < 1000000000; ++i)
    {
        doAction(action);
    }
}

方法 2:

doTheWork(int action)
{
    switch(action)
    {
    case 1:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<1>();
        }
        break;
    case 2:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<2>();
        }
        break;
    //-----------------------------------------------
    //... (there are 1000000 cases here)
    //-----------------------------------------------
    case 1000000:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<1000000>();
        }
        break;
    }
}

doAction(int action)関数と関数template<int Action> doAction()が、コンパイル時にインライン化される約 10 行のコードで構成されていると仮定しましょう。呼び出しdoAction(#)doAction<#>()in の機能と同等ですが、テンプレート化されていないものは、コンパイル時に引数の値がわかっている場合にコードで適切な最適化を行うことができるため、 doAction(int value)よりもやや遅くなります。template<int Value> doAction()

したがって、私の質問は、テンプレート化された関数の場合、数百万行のコードすべてが CPU L1 キャッシュ (およびそれ以上) を埋めるか (したがって、パフォーマンスが大幅に低下するか)、またはdoAction<#>()現在実行されているループ内の行のみが取得されるかどうかです。キャッシュされた?

4

2 に答える 2

2

これは、実際のコード サイズ (10 行のコードが少ない場合も多い場合もあります) と、もちろん実際のマシンに依存します。

ただし、方法 2 は、この数十年の経験則に激しく違反しています。つまり、命令は安価ですが、メモリ アクセスはそうではありません。

スケーラビリティの制限

通常、最適化は直線的です。実行時間の 10%、20%、場合によっては 30% も削減できます。キャッシュ制限に達することは、「レンガの壁にぶつかる」非線形のように、非常に非線形です。

コード サイズが第 2 レベルまたは第 3 レベルのキャッシュのサイズを大幅に超えるとすぐに、方法 2 では大きな時間が失われます。これは、以下のハイエンド コンシューマー システムの見積もりが示すとおりです。

  • 10667MB/sピーク メモリ帯域幅を備えた DDR3-1333 、
  • Intel Core i7 Extreme with ~75000 MIPS

10667MB / 75000M = 1 命令あたり 0.14 バイトのブレーク イーブンが得られます。

典型的な x86 命令サイズは 2..3 バイトで、1..2 サイクルで実行されます (x86 命令は分割されているため、これは必ずしも同じ命令であるとは限りません。それでも...) 典型的な x64 命令の長さはさらに大きくなります。 .

キャッシュはどの程度役に立ちますか?
次の数値を見つけました (ソースが異なるため、比較が困難です): i7 Nehalem L2 キャッシュ (256K、>200GB/秒の帯域幅) は、x86 命令にはほぼ対応できますが、おそらく x64 には対応できません。

さらに、L2 キャッシュが完全に機能するのは、次の場合のみです。

  • 次の命令が完全に予測されている、初回実行時のペナルティがなく、キャッシュに完全に適合している
  • 大量のデータが処理されていない
  • 「内部ループ」には重要な他のコードはありません
  • このコアで実行中のスレッドはありません

それを考えると、特にキャッシュが小さい CPU/ボードでは、はるかに早い段階で負ける可能性があります。

于 2010-07-28T21:41:13.983 に答える
1

L1 命令キャッシュには、最近フェッチされた命令、または近い将来の実行が予想される命令のみが含まれます。そのため、コードがそこにあるという理由だけで、2 番目の方法で L1 キャッシュをいっぱいにすることはできません。実行パスにより、実行中の現在のループを表すテンプレートのインスタンス化されたバージョンが読み込まれます。次のループに進むと、通常、LRU キャッシュ ラインが無効になり、次に実行するものに置き換えられます。

つまり、両方のメソッドのループの性質により、L1 キャッシュは両方のケースで見事に機能し、ボトルネックにはなりません。

于 2010-07-27T17:31:49.353 に答える