2017-01-23 14 views
0

インラインコードのレジスタ使用を、以下のコードサンプルでIterator::Incrから最適化しないのはなぜですか? (Visual Studioの2015年、/ O2最適化設定)インラインクラスメソッドのレジスタ最適化が不十分

私はイテレータ、Iterator::IncrInterpolator::InterpolateFast内部インライン化されるメソッドへの参照を使用して、この記事の一番下に示すC++のコードを使用しています。しかし、生成されたアセンブラコードは、へのアクセスへのアクセスを示していますが、このクラスメンバ変数は効率のために内側ループのレジスタの内部に配置できます。私は反復子がコピーされる高速バージョンをコンパイルしていた場合

[email protected]: 
    00020 41 01 19  add  DWORD PTR [r9], ebx ; memory access to Iterator::_rem (slow) 
    00023 4d 63 01  movsxd r8, DWORD PTR [r9] 
    ... 
    0003e 75 e0  jne  SHORT [email protected] 

、アセンブラコードは、プロセッサレジスタの内部を配置及び配列要素自体に一つだけのメモリアクセスを使用します。

[email protected]: 
; 699 :  _rem += _incr; 
    00011 45 03 c2  add  r8d, r10d ; Iterator::_rem placed inside registers (fast) 
... 

イテレータクラスへの参照を使用する場合、コンパイラはクラスが(同時スレッドによって、例えば)InterpolateFast修正またはアクセスされていると仮定し、従って最適化を登録回避するように、それはそう。

イテレータをコピーせずにプロセッサレジスタを使用してインラインメソッドを最適化するにはどうすればよいですか?

typedef unsigned int BYTE; 

class Iterator 
{ 
public: 
    Iterator() {} 
    Iterator (const Iterator& it) : 
     _rem (it._rem), _incr (it._incr) {} 

    inline int Incr (const BYTE* &pSrc) 
    { 
     _rem += _incr; 
     pSrc += _rem >> 16; 
     return _rem; 
    } 

private: 
    int _rem; 
    int _incr; 
    friend class Interpolator; 
}; 

class Interpolator 
{ 
public: 
    Interpolator (BYTE* p) : _p (p) {} 
    int InterpolateFast (int len) 
    { 
     int sum = 0; 
     const BYTE *p = _p; 
     Iterator& it (_it); // slow version, memory accesses to it._rem 
     // Iterator it (_it); // fast version, registers optimized 
     while (len--) 
     { 
      int rem = it.Incr (p); 
      sum += p[0] * rem; 
     } 
     return sum; 
    } 

private: 
    Iterator _it; 
    const BYTE* _p; 
}; 

int main() 
{ 
    BYTE arr[1000]; 
    Interpolator ip (arr); 
    volatile int sum = ip.InterpolateFast (1000); 
    return 0; 
} 

(コードが、このポストのために簡略化されており、意味のある機能を有していないことに留意されたい。)

+0

はコンパイルされません。 MCVEを投稿できますか? –

+0

ポインタエイリアシングの問題のようです - 私はM $コンパイラのオプションを知らないけど、/ Oaや/ Owを試してみて、 "遅い"バージョンに役立つかどうかを見てください。はいの場合は、おそらくコードの変更を介して可能性があります。 – Anty

+0

@RichardHodges:例を更新しました。 –

答えて

1

問題は、関数の終了時に、_it通話によって修飾されていなければならないことですIncrにこのコピーでは、更新が必要なオブジェクトのみが破棄されるため、更新は外部からは見えません。

明らかに、コンパイラがループの終わりにメモリを更新する可能性がありますが、初期のリターンや例外があると難しいです。あなたと私はここには当てはまりませんが、コンパイラが紛失しているかもしれません。

さらに複雑な呼び出しがループ内にある場合、コンパイラは_itがループ内で更新されるようにする必要があるという問題もあります。

最も簡単なworkroundは次のとおりです。

ちなみに
Iterator it (_it); // fast version, registers optimized 
    while (len--) 
    { 
     int rem = it.Incr (p); 
     sum += p[0] * rem; 
    } 
    _it = it; // ****** Added line 
    return sum; 

、あなたはメモリアクセスが遅いことを示しているタイミングを持っているのですか?最初の反復の後、それはレジスタよりもはるかに遅くないL1キャッシュに置かれます。 (This answerはおそらく追加の3〜5サイクルを示唆しており、他のものと重複する可能性があります)

+0

コピーコンストラクタの回避策は役立ちますが、より複雑なオリジナルコード(ここでは投稿できません)では、依然として不要なメモリアクセスが1つあります。私は実際に回避策を使用せずに問題を解決したいのですが、その場合、生成されたコードが良くなると思うからです。 「遅い」バージョンと「速い」バージョンのパフォーマンスの差は約20%で、コードベースの大部分に影響します。 –

+0

OK、測定値があります。それは良い。あなたはMSVCよりもむしろインテルコンパイラの使用を検討しましたか? (あるいは、おそらくVS2017コンパイラです。)それ以外にも、コンパイラが必要な出力を生成するまで、あるいはインラインアセンブラを書くまで、C++を調整する必要があります。 –

+0

新しいコンパイラを現在のビルドプロセスに統合するのは難しいでしょう。 64ビットコードを使用する場合、インラインアセンブラは使用できません。外部アセンブラファイルをセットアップする必要があります。 InterpolateFastメソッドが私のバージョンでテンプレート化されているので、これはむしろ複雑です。 私は、コードを最適化するための特別なコンパイラフラグ、プラグマなどの希望をあきらめていません。 –

関連する問題