我一直在努力使用以下代码。它是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中获得的东西。
答案 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
你可以很简单地包装任何可以在惰性(...)表达式中重复计算的昂贵计算,然后你可以根据需要强制计算多次,并且只计算一次。