StackOverflow如果使用Parallel.For

时间:2015-03-18 06:51:45

标签: c# recursion parallel.for

我目前正在尝试制作代码,这将有助于计算为StarMade游戏设置的高效反应堆。 我使用递归方法来探索元素的三维树并找到所有相关的组。对于前者group - 它是一组元素,彼此保持紧密。 在图片上是这样的:

XOX
OOX
XXO

其中O不存在,X是reactor(元素)。 在这张照片上有3组元素。 [0,0],[2,0] - [2,1],[0,2] - [1,2] 另一种变体:

XXX
OOX
XXX

这里只有一个组,因为所有元素都保持彼此靠近。 这是我的代码:

 void CheckGroup(int x, int y, int z, Group group)
    {
        if(x >= maxz || x < 0 || y >= maxy || y < 0 || z >= maxz || z < 0)
        {
            return;
        }

        if (reactorsChecked[x, y, z])
        {
            return;
        }
        reactorsChecked[x, y, z] = true;
        if (reactors[x, y, z])
        {
            if (group == null)
            {
                group = new Group();
                group.MaxX = x;
                group.MaxY = y;
                group.MaxZ = z;
                group.MinX = x;
                group.MinY = y;
                group.MinZ = z;
                group.Blocks = 1;
            }
            else
            {
                group.MaxX = Math.Max(group.MaxX, x);
                group.MaxY = Math.Max(group.MaxY, y);
                group.MaxZ = Math.Max(group.MaxZ, z);
                group.MinX = Math.Min(group.MinX, x);
                group.MinY = Math.Min(group.MinY, y);
                group.MinZ = Math.Min(group.MinZ, z);
                group.Blocks += 1;
            }

            CheckGroup(x + 1, y, z, group);
            CheckGroup(x - 1, y, z, group);

            CheckGroup(x, y + 1, z, group);
            CheckGroup(x, y - 1, z, group);

            CheckGroup(x, y, z + 1, group);
            CheckGroup(x, y, z - 1, group);

            if (!groups.Contains(group))
            {
                groups.Add(group);
            }
        }
    }

group - 是集群的简单类,它存储有关此集群中的元素计数的数据以及此集群的边界框。 reactorsChecked - 是简单的bool [,,]数组,存储有关元素的信息,我们已经检查过,以避免双打 reactor - 简单的bool [,,]随机元素数组。 首先,我将随机值插入reactor数组,然后调用CheckGroup(x,y,z,null)。如果反应堆阵列大小小于25x25x25,那么一切正常。单线程大小的数组可能是100x100x100,一切都会好的。但是,如果我尝试使用Parallel.For,那么在接近9000次递归后我得到了StackOverflow ... 这是完整的代码:

Parallel.For(0, Environment.ProcessorCount, (i) =>
            {
                Calculator calc = new Calculator(x, y, z, max, cycles);
                calcs.Add(calc);
            });

public class Calculator
{
    Random rnd = new Random();
    //List<Group> groups = new List<Group>();
    HashSet<Group> groups = new HashSet<Group>();
    bool[, ,] reactors;
    public bool[, ,] reactorsMax;
    bool[, ,] reactorsChecked;
    public double maxEnergy = 0;
    public string result = "";
    public string resultPic = "";

    int maxx, maxy, maxz;

    public Calculator(int x, int y, int z, int max, int cycles)
    {
        maxx = x;
        maxy = y;
        maxz = z;
        maxEnergy = max;
        for (int i = 0; i < cycles; i++)//check few variants per thread
        {
            Calculate(x,y,z);
        }
    }

    private void Calculate(int X, int Y, int Z)
    {
        //groups = new List<Group>();
        groups = new HashSet<Group>();

        reactors = new bool[X, Y, Z];

        for (int x = 0; x < X; x++)
        {
            for (int y = 0; y < Y; y++)
            {
                for (int z = 0; z < Z; z++)
                {
                    reactors[x, y, z] = rnd.Next(2)==1;//fill array with random values
                }
            }
        }

        reactorsChecked = new bool[X, Y, Z];
        for (int x = 0; x < X; x++)
        {
            for (int y = 0; y < Y; y++)
            {
                for (int z = 0; z < Z; z++)
                {
                    CheckGroup(x, y, z, null);//start calculations
                }
            }
        }
        double sum = 0;
        int blocks = 0;
        foreach(Group g in groups)
        {
            float dims = g.MaxX - g.MinX + g.MaxY - g.MinY + g.MaxZ - g.MinZ + 3;
            sum += (2000000.0f / (1.0f + Math.Pow(1.000696f, (-0.333f * Math.Pow((dims / 3.0f), 1.7)))) - 1000000.0f + 25.0f * g.Blocks);
            blocks += g.Blocks;
        }

        if (sum > maxEnergy)
        {
            maxEnergy = sum;

            reactorsMax = reactors;
        }

    }

    void CheckGroup(int x, int y, int z, Group group)
    {
        if(x >= maxz || x < 0 || y >= maxy || y < 0 || z >= maxz || z < 0)
        {
            return;
        }

        if (reactorsChecked[x, y, z])
        {
            return;
        }
        reactorsChecked[x, y, z] = true;
        if (reactors[x, y, z])
        {
            if (group == null)
            {
                group = new Group();
                group.MaxX = x;
                group.MaxY = y;
                group.MaxZ = z;
                group.MinX = x;
                group.MinY = y;
                group.MinZ = z;
                group.Blocks = 1;
            }
            else
            {
                group.MaxX = Math.Max(group.MaxX, x);
                group.MaxY = Math.Max(group.MaxY, y);
                group.MaxZ = Math.Max(group.MaxZ, z);
                group.MinX = Math.Min(group.MinX, x);
                group.MinY = Math.Min(group.MinY, y);
                group.MinZ = Math.Min(group.MinZ, z);
                group.Blocks += 1;
            }

            CheckGroup(x + 1, y, z, group);
            CheckGroup(x - 1, y, z, group);

            CheckGroup(x, y + 1, z, group);
            CheckGroup(x, y - 1, z, group);

            CheckGroup(x, y, z + 1, group);
            CheckGroup(x, y, z - 1, group);

            if (!groups.Contains(group))
            {
                groups.Add(group);
            }
        }
    }

}

所以主要的问题是 - 是否有可能避免在Parallel.For中使用stackOverflow,或者将其重写为迭代循环?

Parallel.For使用默认的stackSize值,即使你将使用

Thread(()=>
{
    Parallel.For(...);
},stackSize).Start()

它将使用默认值...

我不喜欢这样的变体:

for(int i = 0; i < cpuCount; i++)
{
    Thread t = new Thread(()=>{calculate();},stackSize).Start()
}

因为我必须管理所有线程,等待所有线程完成,所以它使代码变得非常复杂......可能有更简单的事情吗?

1 个答案:

答案 0 :(得分:1)

有两种选择:

  1. 使用递归并尝试增加堆栈大小(使用Thread(ThreadStart, maxStackSize)构造函数)。应用程序中的堆栈通常设置为1MB(有关详细信息,请参阅this link)。特别是在没有优化的DEBUG模式下(没有内联优化完成),这是一个非常有限的值。为每个Paralllel.For()语句创建一个具有单独堆栈的线程 可能有帮助。

  2. 使用迭代外观代替递归来自行处理堆栈深度。

  3. 我个人会选择选项1.(有或没有单独的堆栈),以防我知道递归的最大深度。

    在大多数情况下,我喜欢的解决方案是迭代方法。

    由@LordXaosa编辑: 我试过这个,一切正常

    int stackSize = 1024*1024*1024;//1GB limit
    ManualResetEvent[] mre = new ManualResetEvent[Environment.ProcessorCount];
    Parallel.For(0, Environment.ProcessorCount, (i) =>
    {
         mre[i] = new ManualResetEvent(false);
         Thread t = new Thread((object reset) =>
         {
               Calculator calc = new Calculator(x, y, z, max, cycles);
               calcs.Add(calc);
               ManualResetEvent m = (ManualResetEvent)reset;
               m.Set();
         }, stackSize / (Environment.ProcessorCount * 4));
         t.Start(mre[i]);
    });
    WaitHandle.WaitAll(mre);
    

    但也有一个限制...... 50x50x50阵列工作正常,但更多 - 堆栈溢出...在原始游戏中它可以处理1000x1000x1000集,所以可能有另一种算法。 谢谢你的帮助!