Развертывание вложенных циклов в F # - PullRequest
4 голосов
/ 31 января 2010

Я боролся со следующим кодом. Это реализация 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   

Хотя это работает, это не совсем "функционально". Он также страдает от ужасной производительности, он в 2,5 раза медленнее, чем эквивалентный код C #. body - это массив структур типа Body.

С чем я борюсь, так это с тем, что force () является дорогой функцией, поэтому обычно вы рассчитываете ее один раз для каждой пары и полагаетесь на то, что Fij = -Fji. Но это действительно портит любой разворачивающийся цикл и т. Д.

Предложения с благодарностью приняты! Нет, это не домашнее задание ...

Спасибо

Ade

ОБНОВЛЕНО: Для пояснения 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. Вы, вероятно, могли бы использовать класс Vector3D из .NET Framework для этого случая (на самом деле я изучаю его переход).

ОБНОВЛЕНИЕ 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 - худший нарушитель. Вам это не нужно, если sofifyingLength всегда ненулевое, даже если оно очень маленькое (Epsilon). Эта оптимизация была в коде C #, но не в версии F # (дох!).

  • Math.Pow (x, -1.5) кажется намного медленнее, чем 1 / (Math.Sqrt (x) * x). По сути, этот алгоритм немного странный, потому что его производительность продиктована стоимостью одного шага.

  • Перемещение вычисления силы в линию и избавление от некоторых разрывов также дает некоторое улучшение, но производительность действительно снижалась из-за ветвления и во власти стоимости Sqrt.

WRT с использованием классов над структурами: в некоторых случаях (CUDA и нативные реализации этого кода на C ++ и средство визуализации DX9) мне нужно получить массив тел в неуправляемый код или на графический процессор. В этих сценариях способность запоминать непрерывный блок памяти выглядит как путь. Не то, что я получил бы от массива класса Body.

Ответы [ 2 ]

3 голосов
/ 31 января 2010

Я не уверен, стоит ли переписывать этот код в функциональном стиле.Я видел несколько попыток написать функциональное вычисление парных взаимодействий, и за каждым из них было сложнее следовать, чем двум вложенным циклам.

Прежде чем смотреть на структуры и классы (я уверен, что кто-то ещечто-то умное сказать по этому поводу), может быть, вы можете попробовать оптимизировать сам расчет?

Вы рассчитываете две дельты ускорения, назовем их dAi и dAj:

dAi = r * m1 *м2 / (мм * кв.мр (мм)) / м2

дАдж = r * м2 * м2 / (м2 * м2 (мкм)) / м2

[примечание: m1 = тела. [i] .mass, m2 = тел. [j] .mass]]

Деление по массе сводится к следующему:

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

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

Теперь вам нужно только вычислить r / (rm sqrt (rm)) для каждой пары (i, j). Это может быть оптимизировано в дальнейшем, потому что 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 голос
/ 31 января 2010

Я бы хотел поэкспериментировать с вашим кодом, но это сложно, так как определения Body и FloatVector отсутствуют, и они также отсутствуют в оригинальном сообщении в блоге, на которое вы указываете.

Я бы рискнул предположить, что вы могли бы улучшить свою производительность и переписать в более функциональном стиле, используя ленивые вычисления F #: http://msdn.microsoft.com/en-us/library/dd233247(VS.100).aspx

Идея довольно проста: вы оборачиваете любые дорогостоящие вычисления, которые можно многократно вычислять, в ленивое (...) выражение, тогда вы можете форсировать вычисления столько раз, сколько захотите, и они будут вычисляться только один раз. *

...