0

事前にサイズ設定された 2 つの単純なスタックを実装するクラスがあります。これらは、コンストラクターによって事前にサイズ設定された vector 型のクラスのメンバーとして格納されます。これらは小さく、キャッシュ ライン サイズに適したオブジェクトです。これらの 2 つのスタックはサイズが一定で、永続化され、遅延更新されます。多くの場合、計算コストの低いメソッドによって一緒にアクセスされますが、これらのメソッドは何度も (1 秒あたり数万回から数十万回) 呼び出すことができます。

すべてのオブジェクトはすでに良好な状態にあり (コードはクリーンで、本来の動作を実行します)、すべてのサイズは制御されています (ほとんどの場合、結果を含む操作のチェーン全体で 64k から 128K ですが、256k に近づくことはめったにありません。 L2 ルックアップ、多くの場合 L1)。

いくつかの自動ベクトル化が機能しますが、それ以外は全体がシングル スレッド コードです。

クラスは、いくつかのマイナーなものとパディングを除いて、次のようになります。

class Curve{
private:
    std::vector<ControlPoint> m_controls;
    std::vector<Segment> m_segments;

    unsigned int m_cvCount;
    unsigned int m_sgCount;
    std::vector<unsigned int> m_sgSampleCount;

    unsigned int m_maxIter;
    unsigned int m_iterSamples;
    float m_lengthTolerance;

    float m_length;
}

Curve::Curve(){
    m_controls = std::vector<ControlPoint>(CONTROL_CAP);
    m_segments = std::vector<Segment>( (CONTROL_CAP-3) );

    m_cvCount = 0;
    m_sgCount = 0;
    std::vector<unsigned int> m_sgSampleCount(CONTROL_CAP-3);

    m_maxIter = 3;
    m_iterSamples = 20;
    m_lengthTolerance = 0.001;

    m_length = 0.0;
}

Curve::~Curve(){}

冗長に耐えてください。私は自分自身を教育し、中途半端な知識で操作していないことを確認しようとしています。

それらに対して実行される操作とそれらの実際の使用を考えると、パフォーマンスは主にメモリ I/O バウンドです。データの最適な配置に関連するいくつかの質問があります。これは Intel CPU (Ivy といくつかの Haswell) であり、GCC 4.4 を使用していることに注意してください。これに関する他の使用例はありません。

コントロールとセグメントの実際のストレージが Curve のインスタンスに隣接している場合、これはキャッシュの理想的なシナリオであると想定しています (サイズに関しては、ターゲット CPU に簡単に収まります)。関連する前提として、ベクトルが Curve のインスタンスから離れていて、それらの間にある場合、メソッドがこれら 2 つのメンバーのコンテンツに交互にアクセスするため、より頻繁にエビクションと L1 キャッシュの再読み込みが行われるというものがあります。

1)それは正しいですか(データは、新しい操作で最初に検索されたアドレスからキャッシュサイズの全体にわたってプルされ、適切なサイズの便利な複数のセグメントではありません)、またはキャッシングメカニズムとキャッシュを誤解していますかラムの複数の小さなストレッチを引っ張って保持できますか?

2)上記に従って、純粋な状況では、すべてのテストは常にクラスのインスタンスと隣接するベクトルで終了しますが、統計的に可能性が高いとはいえ、それはただの運だと思います。通常、クラスをインスタンス化すると、そのオブジェクトのスペースのみが予約され、ベクトルは次に使用可能な空き連続チャンクに割り当てられます。これは、以前にメモリ内に小さな空のニッチが見つかった場合、Curve インスタンスの近くにあるとは限りません。これは正しいです?

3) 1 と 2 が正しい、または機能的に十分に近いと仮定すると、クラス オブジェクト自体がインスタンス化時に十分な大きさであることを確認するためにある種のアロケータを記述し、そこにベクトルをコピーする必要があることを理解しています。私自身とそこからそれらを参照してください。それが問題を解決する唯一の方法である場合は、おそらくそのような方法をハックすることができますが、そのようなことを行うための素晴らしい/スマートな方法がある場合は、恐ろしくハックしたくありません。ベストプラクティスと提案された方法に関する指針は非常に役立ちます(「mallocを使用しないでください。連続しているとは限りません」、私がすでにダウンしているもの:))。

4

1 に答える 1

0
  1. Curve インスタンスが 1 つのキャッシュ ラインに収まり、2 つのベクトルのデータもそれぞれ 1 つのキャッシュラインに収まる場合、4 つの定数キャッシュラインがあるため、状況はそれほど悪くありません。すべての要素が間接的にアクセスされ、メモリ内でランダムに配置された場合、要素へのアクセスごとにフェッチ操作が発生する可能性がありますが、その場合は回避されます。Curve とその要素の両方が 4 つ未満のキャッシュラインに収まる場合は、それらを連続したストレージに配置することでメリットが得られます。

  2. 真実。

  3. std::array を使用した場合、要素が所有するクラスに埋め込まれ、動的な割り当てがないことが保証されます (それ自体がメモリ スペースと帯域幅を消費します)。これにより、ベクトル コンテンツを Curve インスタンスと連続したストレージに配置する特別なアロケータを使用した場合に引き続き発生する間接アクセスを回避することもできます。

ところで:短いスタイルのコメント:

Curve::Curve()
{
  m_controls = std::vector<ControlPoint>(CONTROL_CAP, ControlPoint());
  m_segments = std::vector<Segment>(CONTROL_CAP - 3, Segment());
  ...
}

...次のように記述します。

Curve::Curve():
  m_controls(CONTROL_CAP),
  m_segments(CONTROL_CAP - 3)
{
  ...
}

これは「初期化リスト」と呼ばれます。詳細については、その用語を検索してください。また、2 番目のパラメーターとして指定するデフォルトで初期化された要素は、既にデフォルトであるため、明示的に指定する必要はありません。

于 2015-02-27T07:23:03.187 に答える