在F#中展开嵌套循环

时间:2010-01-31 06:41:06

标签: f# physics

我一直在努力使用以下代码。它是Forward-Euler algorithm的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倍。 bodies是Body类型的结构数组。

我正在努力的是force()是一个昂贵的函数,所以通常你为每一对计算一次并依赖于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; }
}

向量定义了您期望的标准Vector类的运算符类型。你可以在这种情况下使用.NET框架中的Vector3D类(我实际上正在调查切换到它)。

更新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)慢很多。基本上这个算法有点奇怪,因为它的性能是由这一步的成本决定的。

  • 内联移动力计算并消除一些分歧也会带来一些改善,但性能确实被分支所杀,并且由Sqrt的成本占主导地位。

在结构上使用类的WRT:有些情况(此代码的CUDA和本机C ++实现以及DX9渲染器)我需要将主体数组转换为非托管代码或GPU。在这些情况下,能够记忆连续的内存块似乎是要走的路。不是我从一类Body中获得的东西。

2 个答案:

答案 0 :(得分:3)

我不确定以功能样式重写此代码是否明智。我已经看到一些尝试以函数方式编写对交互计算,并且每个都比两个嵌套循环更难以遵循。

在查看结构与类之前(我确定其他人对此有一些明智的说法),也许您可​​以尝试优化计算本身?

你正在计算两个加速度增量,我们称之为dAi和dAj:

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

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

[注意:m1 = body。[i] .mass,m2 = body。[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

看,马,没有更多的分歧!

警告:未经测试的代码。请自担风险。

答案 1 :(得分:1)

我想用你的代码来玩arround,但是由于缺少Body和FloatVector的定义而且它们似乎在你所指向的原始博客帖子中也没有出现,所以很难。

我猜测你可以使用F#的懒惰计算来改善你的表现并用更实用的方式重写: http://msdn.microsoft.com/en-us/library/dd233247(VS.100).aspx

你可以很简单地包装任何可以在惰性(...)表达式中重复计算的昂贵计算,然后你可以根据需要强制计算多次,并且只计算一次。