2016-08-07 8 views
-3

最近、ソフトラスターレンダラーを書いていますが、速度はとても遅いです。パフォーマンステストでは、float lerp関数がボトルネックであることがわかりました。どのようにこの機能の速度を向上させるには? simdを使用しますか?何か案が?どのようにフロートlerp機能の速度を向上させるには?

inline float MathUtil::Lerp(float x1, float x2, float t) 
{ 
    return x1 + (x2 - x1)*t; 
} 

//lerp vector 
ZCVector MathUtil::Lerp(const ZCVector& v1, const ZCVector& v2, float t) 
{ 
    return ZCVector(
     Lerp(v1.x, v2.x, t), 
     Lerp(v1.y, v2.y, t), 
     Lerp(v1.z, v2.z, t), 
     v1.w 
    ); 
} 

//lerp ZCFLOAT2 
ZCFLOAT2 MathUtil::Lerp(const ZCFLOAT2& v1, const ZCFLOAT2& v2, float t) 
{ 
    return ZCFLOAT2(
     Lerp(v1.u, v2.u, t), 
     Lerp(v1.v, v2.v, t) 
    ); 
} 

//lerp ZCFLOAT3 
ZCFLOAT3 MathUtil::Lerp(const ZCFLOAT3& v1, const ZCFLOAT3& v2, float t) 
{ 
    return ZCFLOAT3(
     Lerp(v1.x, v2.x, t), 
     Lerp(v1.y, v2.y, t), 
     Lerp(v1.z, v2.z, t) 
    ); 
} 

//lerp VertexOut 
VertexOut MathUtil::Lerp(const VertexOut& v1, const VertexOut& v2, float t) 
{ 
    return VertexOut(
     Lerp(v1.posTrans, v2.posTrans, t), 
     Lerp(v1.posH, v2.posH, t), 
     Lerp(v1.tex, v2.tex, t), 
     Lerp(v1.normal, v2.normal, t), 
     Lerp(v1.color, v2.color, t), 
     Lerp(v1.oneDivZ, v2.oneDivZ, t) 
    ); 
} 

VertexOutの構造:

class VertexOut 
{ 
public: 

    ZCVector posTrans; 

    ZCVector posH; 

    ZCFLOAT2 tex; 

    ZCVector normal; 

    ZCFLOAT3 color; 

    float oneDivZ; 
} 

scanlinefill関数は三角形を埋めるためには、すべての頂点はlerp機能を使用する必要があるので、それはそう何度も呼び出されます。整数xIndexを増分することによって、ループ(より正確に)、代わり

for (float x = left.posH.x; x <= right.posH.x; x += 0.5f) { 
     int xIndex = static_cast<int>(x + .5f); 
     ... 
} 

を、各xIndexための右float xを計算する:

void Tiny3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right, int yIndex) 
{ 
    float dx = right.posH.x - left.posH.x; 

    for (float x = left.posH.x; x <= right.posH.x; x += 0.5f) 
    { 
     int xIndex = static_cast<int>(x + .5f); 
     if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth()) 
     { 

      float lerpFactor = 0; 
      if (dx != 0) 
      { 
       lerpFactor = (x - left.posH.x)/dx; 
      } 


      float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor); 
      if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex)) 
      { 
       m_pDevice->SetZ(xIndex, yIndex, oneDivZ); 
       //lerp get vertex 
       VertexOut out = MathUtil::Lerp(left, right, lerpFactor); 
       out.posH.y = yIndex; 

       m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out)); 
      }   
     } 
    } 
} 
+0

おそらくビット単位での手探りを試してください。早すぎる最適化を間違った場所で試してはいけません。 –

+1

あなたはどのプラットフォームを最適化していますか? x86?古いCPUで動作するバイナリが必要ですか、またはAVXとFMAを使用できますか?どのようなコンパイラとオプション?さらに重要なのは、周囲のコードは何ですか?それは実際に自動ベクトル化しますか?この関数自体は、SSEまたは他のSIMD ISAを用いて自明にベクトル化する必要があります。これが埋め込む周囲のコードは明らかに重要です。遅延はスループットの制限要因ですか? –

+1

@πάνταῥεῖ:FP命令間の整数ビット・バイディングは、通常、現代のCPUでは良い考えではありません。 x86では、スカラー浮動小数点でもXMMレジスタを使用するため、SIMD整数命令で処理するか、XMMと整数レジスタ間でゆっくりと往復させる必要があります。 FP insns間で整数ベクトル命令を使用することにより余分なバイパス遅延があるので、1つまたは2つの整数命令で有用なことができたとしても、単にFPを使用するよりもレイテンシが悪くなります。さらに、ベクトルFP命令は非常に高いスループットです。例えばHaswellのクロックごとに2つのベクトルFMA。 –

答えて

1

このループ構造は、潜在的に、必要に応じlerp 2倍の回数実行されます。


これかもしれない自動ベクトル化、しかし、あなたは何が起こったかを見るためにあなたのコンパイラの出力をチェックする必要があると思います。うまくいけば、out.posH.y = yIndex;で上書きしたLerpは、結果を破棄してから最適化されてしまいます。そうでない場合、そのLerpをしないラッパー関数を作ることからスピードアップを得るかもしれません。あなたはそれがより多くのSIMD優しい構造体・オブ・アレーを使用して作ることができる


は、構造体の連続のためにすべてを保持し、あなたのいるAoSアプローチの代わりに接近します。しかし、同じ方法で複数の要素をLerpしているので、2つのスカラーベクトルと1つのベクトルLerpを使って自動ベクトル化するかもしれません。

very nice beginner/intermediate set of slidesへのリンクを含め、SIMDに関するガイドについては、タグウィキを参照してください。


おそらく他にも変更できるものがあります。 全体的な作業が少なくて済むようにコードの再構成が大きくなるこの種の最適化では、SIMDを使用して現代のCPUの無理な力を効率的に適用するよりも、さらに高速化することができます。

スピードアップを倍増させるために両方を同時に行うのは、本当に速くなるものです。

キャッシュミスとメモリ帯域幅のボトルネックはしばしば大きな要因です。そのため、アクセスパターンを最適化すると大きな違いが生じます。

低レベルの詳細については、Agner Fog's optimization guideを参照してください。彼はC++最適化ガイドを持っていますが、良いものの大部分はx86 asmに関するものです。 (タグwikiも参照してください)。しかし、この低レベルの最適化は、の後、の後に高レベルの最適化を探していることを覚えておいてください。

関連する問題