4

私は次のコードで苦労しています。これは、重力場で移動する星のモデリングに使用されるForward-EulerアルゴリズムのF#実装です。

let force (b1:Body) (b2:Body) = 
    let r = (b2.Position - b1.Position)
    let rm = (float32)r.MagnitudeSquared + softeningLengthSquared
    if (b1 = b2) then
        VectorFloat.Zero
    else
        r * (b1.Mass * b2.Mass) / (Math.Sqrt((float)rm) * (float)rm)    

member this.Integrate(dT, (bodies:Body[])) = 

    for i = 0 to bodies.Length - 1 do
        for j = (i + 1) to bodies.Length - 1 do
            let f = force bodies.[i] bodies.[j]
            bodies.[i].Acceleration <- bodies.[i].Acceleration + (f / bodies.[i].Mass)
            bodies.[j].Acceleration <- bodies.[j].Acceleration - (f / bodies.[j].Mass)
        bodies.[i].Position <- bodies.[i].Position + bodies.[i].Velocity * dT
        bodies.[i].Velocity <- bodies.[i].Velocity + bodies.[i].Acceleration * dT   

これは機能しますが、正確には「機能的」ではありません。それはまたひどいパフォーマンスに苦しんでいます、それは同等のc#コードより2.5倍遅いです。bodyは、Bodyタイプの構造体の配列です。

私が苦労しているのは、force()は高価な関数であるため、通常はペアごとに1回計算し、Fij=-Fjiであるという事実に依存していることです。しかし、これは実際にループの展開などを台無しにします。

ありがたい提案をいただきました!いいえ、これは宿題ではありません...

ありがとう、

アデ

更新: BodyとVectorFloatを明確にするためにC#構造体として定義されています。これは、プログラムがF#/ C#とC ++/CLIの間で相互運用するためです。最終的にはBitBucketにコードを追加する予定ですが、これは進行中の作業であり、作成する前に解決する必要のある問題がいくつかあります。

[StructLayout(LayoutKind.Sequential)]
public struct Body
{
    public VectorFloat Position;
    public float Size;
    public uint Color;

    public VectorFloat Velocity;
    public VectorFloat Acceleration;
              '''
}   

[StructLayout(LayoutKind.Sequential)]
public partial struct VectorFloat
{
    public System.Single X { get; set; }
    public System.Single Y { get; set; }
    public System.Single Z { get; set; }
}

ベクトルは、標準のベクトルクラスに期待する演算子の種類を定義します。この場合、おそらく.NET FrameworkのVector3Dクラスを使用できます(私は実際にそれに切り替わることを調査しています)。

更新2:以下の最初の2つの応答に基づいてコードを改善しました。

    for i = 0 to bodies.Length - 1 do
    for j = (i + 1) to bodies.Length - 1 do
        let r = ( bodies.[j].Position -  bodies.[i].Position)
        let rm = (float32)r.MagnitudeSquared + softeningLengthSquared
        let f = r / (Math.Sqrt((float)rm) * (float)rm)    
        bodies.[i].Acceleration <- bodies.[i].Acceleration + (f * bodies.[j].Mass)
        bodies.[j].Acceleration <- bodies.[j].Acceleration - (f * bodies.[i].Mass)
    bodies.[i].Position <- bodies.[i].Position + bodies.[i].Velocity * dT
    bodies.[i].Velocity <- bodies.[i].Velocity + bodies.[i].Acceleration * dT  
  • b1 == b2の場合をカバーするフォース関数の分岐は、最悪の違反者です。softeningLengthが非常に小さい場合でも(Epsilon)、常にゼロ以外の場合は、これは必要ありません。この最適化はC#コードに含まれていましたが、F#バージョンには含まれていませんでした(doh!)。

  • Math.Pow(x、-1.5)は、1 /(Math.Sqrt(x)* x)よりもはるかに遅いようです。基本的に、このアルゴリズムは、パフォーマンスがこの1つのステップのコストによって決定されるという点で少し奇妙です。

  • 力の計算をインラインに移動し、いくつかの除算を取り除くこともある程度の改善をもたらしますが、パフォーマンスは実際には分岐によって損なわれ、Sqrtのコストによって支配されていました。

構造体よりもクラスを使用するWRT:ボディの配列をアンマネージコードまたはGPUに取り込む必要がある場合(このコードのCUDAおよびネイティブC ++実装とDX9レンダラー)があります。これらのシナリオでは、メモリの連続したブロックをmemcpyできることは、進むべき道のように思えます。Bodyクラスの配列から得られるものではありません。

4

2 に答える 2

3

このコードを機能的なスタイルで書き直すのが賢明かどうかはわかりません。ペアの相互作用の計算を機能的に記述しようとする試みをいくつか見てきましたが、それぞれが2つのネストされたループよりも追跡が困難でした。

構造体とクラスを比較する前に(他の誰かがこれについて何か賢いことを言っていると確信しています)、計算自体を最適化してみることができますか?

2つの加速デルタを計算しています。それらをdAiおよびdAjと呼びましょう。

dAi = r * m1 * m2 /(rm * sqrt(rm))/ m1

dAj = r * m1 * m2 /(rm * sqrt(rm))/ m2

[注:m1 =bodys。[i].mass、m2=bodys。[j].mass]]

質量による除算は次のようにキャンセルされます。

dAi = r m2 /(rm sqrt(rm))

dAj = r m1 /(rm sqrt(rm))

これで、各ペア(i、j)の r /(rm sqrt(rm))を計算するだけで済みます。1 /(rm sqrt(rm))= 1 /(rm ^ 1.5)= rm ^ -1.5であるため、これをさらに最適化できます。したがって、r'= r *(rm ** -1.5)とすると、次のように 編集します。いいえ、できません。それは時期尚早の最適化です(コメントを参照)。r'= 1.0 /(r * sqrt r)の計算が最速です。

dAi = m2 * r '

dAj = m1 * r '

コードは次のようになります

member this.Integrate(dT, (bodies:Body[])) = 
    for i = 0 to bodies.Length - 1 do
        for j = (i + 1) to bodies.Length - 1 do
            let r = (b2.Position - b1.Position)
            let rm = (float32)r.MagnitudeSquared + softeningLengthSquared            
            let r' = r * (rm ** -1.5)
            bodies.[i].Acceleration <- bodies.[i].Acceleration + r' * bodies.[j].Mass
            bodies.[j].Acceleration <- bodies.[j].Acceleration - r' * bodies.[i].Mass
        bodies.[i].Position <- bodies.[i].Position + bodies.[i].Velocity * dT
        bodies.[i].Velocity <- bodies.[i].Velocity + bodies.[i].Acceleration * dT

ほら、まあ、これ以上の部門はありません!

警告:テストされていないコード。ご自身の責任でお試しください。

于 2010-01-31T09:44:59.200 に答える
1

あなたのコードをいじってみたいのですが、BodyとFloatVectorの定義が欠落していて、あなたが指摘した元のブログ投稿からも欠落しているように見えるので、難しいです。

F#の怠惰な計算を使用して、パフォーマンスを向上させ、より機能的なスタイルで書き直すことができると推測するのは危険です:http: //msdn.microsoft.com/en-us/library/dd233247 (VS.100).aspx

アイデアはかなり単純で、怠惰な(...)式で繰り返し計算される可能性のある高価な計算をラップし、計算を何度でも強制することができ、一度だけ計算されます。

于 2010-01-31T07:55:27.663 に答える