3

I'm using Parallel.Foreach to operate over a large array where each array element corresponds to a line in a bitmap image. However, Parallel.Foreach appears to allocate a fix number of threads, say 6 threads, and gives 1/6 of the array to the first thread, and 1/6 of the array to the next thread in order. example:

[0]=>thread1, [1]=>thread1, [2]=>thread1, [3]=>thread1

[4]=>thread2, [5]=>thread2, [6]=>thread2, [7]=>thread2,

.. and so forth

enter image description here

roughly speaking, want I want is an interlace pattern that alternates between threads for each increment of array index...for example:

[0]=>thread1 [1]=>thread2 [2]=>thread3 [4]=>thread4, [5]=>thread1, [6]=>thread2, etc...

is there a way to change the interlace pattern that Parallel.Foreach assigns to each parallel thread actively executing?

I'm trying to get the image to render using a gif interlaced pattern rather than having the bitmap lines rendered sequential in 6 different lines on the image that fill downwards...

using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;

//…

void button_click() {
    Task.Run(() =>{
        start_task();
    });
}

int ds_height=600;
public bool start_task()
{
    var cpu75 = Convert.ToInt32(Math.Ceiling((
           Environment.ProcessorCount * 0.75) * 1.0));
    //MandelInit();

    int[] ytmp;

    int version = 2;
    if (version == 1) {
        ytmp = MandelYLace();
    }
    else {
        int[] ytmp = new int[ds_height];
        for(int i=0; i < ds_height; i++)
        {
            ytmp[i] = i;
        }
        Parallel.ForEach(
            ytmp, //ylace,
            new ParallelOptions { MaxDegreeOfParallelism = cpu75 },
            yy => {
                //ybuff[yy] = MandelLine(yy);
                //ydone.Enqueue(yy);
            }
        );
        stop = true;
        return true;
}

// Interlace Y-Lines using GIF Interlaced method
int[] MandelYLace()
{
    var ylace = new int[ds_height];
    for (int y = 0, yg=0, yy=0; y < ds_height; y++)
    {
        ylace[y] = yy;

        if (yg == 0 || yg == 1)
            yy += 8;
        else if (yg == 2)
            yy += 4;
        else if (yg == 3)
            yy += 2;

        if (yy >= ds_height)
        {
            yg = (yg + 1) % 4;
            if (yg == 1) yy = 4;
            else if (yg == 2) yy = 2;
            else if (yg == 3) yy = 1; 
            else if (yg == 0) yy = 0; 
        }
    }

    return ylace;
}
Hossein Golshani
  • 1,847
  • 5
  • 16
  • 27
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 2
    Without testing your code, something seems weird about this. If we were reading a stream from a remote server into an `IEnumerable`, for example, there would be no way to skip ahead in that stream, so it would have to assign threads in a round robin or load balanced pattern. Perhaps there's special logic for specifically dealing with arrays. What if you use `Parallel.For` instead? Or create your own method using `yield return`? – ProgrammingLlama Sep 22 '18 at 15:11
  • 5
    Custom partitioners is what you seek https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/custom-partitioners-for-plinq-and-tpl –  Sep 22 '18 at 15:21
  • It looks to me that the `SingleElementPartitioner` implementation provided [here](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.partitioner-1?view=netframework-4.7.2) might do the trick. This should have the effect of passing line by line to be worked on, meaning that when a line is finished, the next line that isn't already being processed will be picked up. – spender Sep 22 '18 at 15:46
  • good article on the subject: http://www.drdobbs.com/windows/custom-parallel-partitioning-with-net-4/224600406 – Bimo Sep 22 '18 at 22:18
  • https://en.wikipedia.org/wiki/Locality_of_reference – Hans Passant Sep 22 '18 at 23:53
  • I think the locality is ok with interlacing because each task action processes exactly one line of image that is 100% contiguous in memory and fits nicely into the cache... I think the hit won't be too big if it reloads the cache between each interation of the task... well ...anyways...that's the theory... after the line is finished processing its ok to throw it out of the cache also... – Bimo Sep 23 '18 at 00:29

1 Answers1

1

Here you can see snapshot of interlace first pass... a few tasks are lagging...thus missing line...they show up eventually:

Code solution:enter image description here

int ds_height=600;

public bool start_task()
{
    var cpu75 = Convert.ToInt32(Math.Ceiling((
           Environment.ProcessorCount * 0.75) * 1.0));

    var partitioner = new InterlacePartitioner(ds_height);

    Parallel.ForEach(
            partitioner,
            new ParallelOptions { MaxDegreeOfParallelism = cpu75 },
            yy => {
                //ybuff[yy] = MandelLine(yy);
                //ydone.Enqueue(yy);
            }
    );
    stop = true;
    return true;
}

Custom PLINQ partitioner that uses a shared IEnumerator to implement gif style line interlacing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Collections;

namespace NetMandelbrot
{
    // This is both the Interlaced Line IEnumerator and IEnumerable 
public class ImageInterlace : 
     IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable
{
    int[] Lines;
    int Pos;

    public ImageInterlace(int ImageHeight)
    {
        Pos = -1;
        Lines = GetLines(ImageHeight);
    }

    // Interlace Y-Lines using GIF Interlaced method
    int[] GetLines(int ImageHeight)
    {
        var ylace = new int[ImageHeight];
        for (int y = 0, yg = 0, yy = 0; y < ImageHeight; y++)
        {
            ylace[y] = yy;

            if (yg == 0 || yg == 1)
                yy += 8;
            else if (yg == 2)
                yy += 4;
            else if (yg == 3)
                yy += 2;

            if (yy >= ImageHeight)
            {
                yg = (yg + 1) % 4;
                if (yg == 1) yy = 4;
                else if (yg == 2) yy = 2;
                else if (yg == 3) yy = 1;
                else if (yg == 0) yy = 0;
            }
        }
        return ylace;
    }

    #region Implementation of IDisposable

    public void Dispose()
    {
    }

    #endregion

    #region Implementation of IEnumerable<int>
    public IEnumerator<int> GetEnumerator()
    {
        return this;
    }
    #endregion

    // Legacy C#
    #region Implementation of IEnumerable
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this;
    }
    #endregion

    #region Implementation of IEnumerator<int>
    public bool MoveNext()
    {
        bool done;
        lock (Lines)
        {
            if (Pos < Lines.Length)
            {
                Pos++;
            }
            done = (Pos < Lines.Length);
        }
        return done;
    }

    public void Reset()
    {
        lock (Lines)
        {
            Pos = -1;
        }
    }

    public int Current
    {
        get
        {
            int nextline;
            lock (Lines)
            {
                if (Pos >= Lines.Length)
                {
                    nextline = -1;
                }
                else
                {
                    nextline = Lines[Pos];
                }
            }
            return nextline;
        }
    }

    // C# Legeacy
    object IEnumerator.Current
    {
        get { return Current; }
    }

    #endregion
}

public class InterlacePartitioner : Partitioner<int>
{
    int ImageHeight = 0;
    ImageInterlace imageinterlace;

    public InterlacePartitioner(int imageHeight)
    {
        ImageHeight = imageHeight;
        imageinterlace = new ImageInterlace(ImageHeight);
    }

    public override IList<IEnumerator<int>> GetPartitions(int partitionCount)
    {
        int i = 0;

        List<List<int>> partz = new List<List<int>>();

        foreach (var yline in imageinterlace)
        {
            partz[i % partitionCount].Add(yline);
            i++;
        }
        return (IList<IEnumerator<int>>)partz;
    }

    public override IEnumerable<int> GetDynamicPartitions()
    {
        return imageinterlace;
    }

    // Not consumable from Parallel.ForEach.
    public override bool SupportsDynamicPartitions
    {
        get
        {
            return true;
        }
    }
} //end of class

}
Bimo
  • 5,987
  • 2
  • 39
  • 61