5

I am using someone else's .NET 4 open source smoothed particle hydrodynamics code and I'm trying to convert it for a Unity project which you can only count on to be up to .NET 2 standards. Unfortunately the code uses the Parallels class (which is awesome!) but he used one of the more obscure overloads. Can anyone see a good way to achieve the same thing in .NET 2 without a huge performance hit?

    Parallel.For(
        0,
        _numActiveParticles,
        () => new Vector2[MAX_PARTICLES],
        (i, state, accumulatedDelta) => calculateForce(_activeParticles[i], accumulatedDelta),
        (accumulatedDelta) =>
        {
            lock (_calculateForcesLock)
            {
                for (int i = _numActiveParticles - 1; i >= 0; i--)
                {
                    int index = _activeParticles[i];
                    _delta[index] += accumulatedDelta[index] / MULTIPLIER;
                }
            }
        }
    );

I think this is what the code does (non threaded):

    for (int i = 0; i < _numActiveParticles; i++)
    {
        Vector2[] temp = new Vector2[MAX_PARTICLES];
        temp = calculateForce(_activeParticles[i], temp);


        for (int k = _numActiveParticles - 1; k >= 0; k--)
        {
            int index = _activeParticles[k];
            _delta[index] += temp[index] / MULTIPLIER;
        }
    }
PorcupineRending
  • 1,245
  • 1
  • 11
  • 16
  • 2
    You might find this helpful http://stackoverflow.com/q/12386686/809009 – Ondrej Janacek Dec 18 '13 at 07:29
  • @OndrejJanacek Thanks that's helpful but I also am not 100% sure what the Parallels version is doing so I want to make sure I translate it correctly. – PorcupineRending Dec 18 '13 at 07:36
  • I don't think your understanding is right. You're not even using 'temp' variable anywhere. Read this: http://msdn.microsoft.com/en-us/library/ff963547.aspx – Erti-Chris Eelmaa Dec 18 '13 at 08:00
  • I think unity is almost 3.5 although it says 2.0. Here is a relevant link http://answers.unity3d.com/questions/472726/what-net-version-is-unity-41-using.html – Alex Dec 18 '13 at 08:12
  • @Erti-ChrisEelmaa That was a typo, the variable I had named "bill" was actually supposed to be "temp" (changed in original post now). Thanks for the link I will take a look – PorcupineRending Dec 18 '13 at 15:52
  • 2
    What's the point in using `Parallel.For` if you're going to `lock` over the entirety of the body? You may as well just use a regular `for`. It'll perform way better as it won't have all of that unnecessary synchronization/threading overhead. – Servy Dec 18 '13 at 15:53
  • @Servy not sure, I didn't write the original code and it wasn't commented. Regardless, it sounds like you understand that overload of Parallel.For() is doing. Can you explain it? – PorcupineRending Dec 18 '13 at 16:37
  • @servy the lock is not around the (inner) loop body, but around the "localFinally". Thus, in general the parallelism is still better then in a plain for loop. – Christian.K Dec 18 '13 at 19:46

1 Answers1

1

Your second code isn't correct. I think the correct code is like this:

var accumulatedDelta= new Vector2[MAX_PARTICLES];

for(int i = 0; i < _numActiveParticles; ++i)
{
    accumulatedDelta = calculateForce(_activeParticles[i], accumulatedDelta);
}

for (int i = _numActiveParticles - 1; i >= 0; i--)
{
    int index = _activeParticles[i];
    _delta[index] += accumulatedDelta[index] / MULTIPLIER;
}

I don't know what .net2 has and what doesn't. But you can simulate Parallel.For yourself.

explanation of this overload of Parallel.For is this:

First parameter: start index of loop

Second parameter: end index of loop

Third parameter: a delegate that will create task local data. for each every thread that Parallel.For use (a task) this delegate will be called and return localInit data.

Fourth parameter: a delegate that act as body of for. In first execution of body delegate, this delegate will retrieve data that is created by previuse delegate (localInit). in every subsequent loop, body delegate can change localInit and then return that to next body execution. In last execution of body delegate, localInit data will be passed to last delegate.

Last parameter: another delegate that will be called per each task, when task has finished it's work. localInit will be passed to this delegate. Because this delegate can be called concurrency by multiple tasks, you must protect your shared data.

Edit:

A version of ParallelFor can be like this:

public static void ParallerFor<TLocal>(int startIndex, int endIndex, Func<TLocal> initData, Func<int, TLocal, TLocal> body, Action<TLocal> finalizer)
    {
        int numThreads = Environment.ProcessorCount;
        int chunkOffset = ((endIndex - startIndex) / numThreads) + 1;

        Task[] tasks = new Task[numThreads];

        Enumerable.Range(0, numThreads).ToList().ForEach(x =>
            {
                int start = x * chunkOffset;
                int end = ((x + 1) * chunkOffset);
                end = end > endIndex ? endIndex : end;

                tasks[x] = Task.Factory.StartNew(() =>
                {
                    TLocal init = initData();

                    for(int i = start; i < end; ++i)
                    {
                        init = body(i, init);
                    }

                    finalizer(init);
                });
            });

        Task.WhenAll(tasks).Wait();
    }
MRB
  • 3,752
  • 4
  • 30
  • 44
  • Using the Task class to reimplement Parallel.For is kind of mood. If you have one, you also have the other (both are .net 4 and up). – Christian.K Dec 18 '13 at 19:30
  • @Christian.K Thank for your mention. I don't know what kind of parallel helpers `.net 2` has, but replacing `task` with manual `thread` starting is a straightforward work. – MRB Dec 18 '13 at 19:34
  • You can check msdn. It will tell you. Note that a task is not the same as a thread (at least use the thread pool and not a plain thread). Generally, reimplementing TPL functionality is non trivial to get correct and efficient. Lots of material about that on the internet. – Christian.K Dec 18 '13 at 19:40