简体   繁体   English

如果使用Parallel.For,则为StackOverflow

[英]StackOverflow if using Parallel.For

I'm currently trying to make a code, that will help to count efficient reactors set for game StarMade. 我目前正在尝试编写代码,这将有助于计算为StarMade游戏设置的有效反应堆。 I'm using recursive method to explore the 3d tree of elements and find all related groups. 我正在使用递归方法探索3d元素树并找到所有相关组。 For ex. 对于前。 group - it's a cluster of elements, that stay close to each other. 组-这是一组元素,彼此保持靠近。 On picture something like this: 在图片上是这样的:

XOX
OOX
XXO

where O is nothing, and X is reactor (element). 其中O为空,X为反应堆(元素)。 On this picture there are 3 groups of elements. 在这张图片上有3组元素。 [0,0], [2,0]-[2,1], [0,2]-[1,2] Another variant: [0,0],[2,0]-[2,1],[0,2]-[1,2]另一个变体:

XXX
OOX
XXX

Here is only one group, because all elements stay close to each other. 这只是一组,因为所有元素都彼此靠近。 Here is my code: 这是我的代码:

 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 - is simple class for cluster, that store data about elements count in this cluster and bounding box of this cluster. group-是集群的简单类,用于存储有关此集群中元素计数的数据以及该集群的边界框。 reactorsChecked - is simple bool[,,] array, that store information about elements, that we have checked, to avoid doubles reactor - simple bool[,,] array of random elements. reactsChecked-是简单的bool [,,]数组,用于存储我们已经检查过的元素的信息,以避免doubles堆-随机元素的简单bool [,,]数组。 At first I insert random values to reactors array, and then call CheckGroup(x,y,z,null). 首先,我将随机值插入反应堆数组,然后调用CheckGroup(x,y,z,null)。 If reactors array size less then 25x25x25, then all ok. 如果反应堆的阵列尺寸小于25x25x25,则一切正常。 In single thread size of array could be 100x100x100 and all would be ok. 在单线程中,数组的大小可以是100x100x100,一切都可以。 But if I try to use Parallel.For, then I got StackOverflow after near 9000 recursions... Here is full code: 但是,如果我尝试使用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);
            }
        }
    }

}

So the main question - is it possible to avoid stackOverflow in Parallel.For, or to rewrite it to iteration loop? 因此,主要问题-是否有可能避免Parallel.For中的stackOverflow或将其重写为迭代循环?

Parallel.For using default stackSize value even if you will use 并行。即使您将使用默认的stackSize值

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

it will use default values... 它将使用默认值...

I don't like variant like this: 我不喜欢这样的变体:

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

because I have to manage all threads, wait while all finishes, so it makes code very complicated... May be there are easier things? 因为我必须管理所有线程,等待所有完成,所以这使代码非常复杂...可能还有更简单的方法吗?

There are two options: 有两种选择:

  1. to use recursion and try to increase the stack size (by using the Thread(ThreadStart, maxStackSize) constructor). 使用递归并尝试增加堆栈大小(通过使用Thread(ThreadStart, maxStackSize)构造函数)。 The stack in applications is usually set to 1MB (see this link for details). 应用程序中的堆栈通常设置为1MB(有关详细信息,请参见此链接 )。 Especially in DEBUG mode without optimizations (no inlining optimization done) this is a very limited value. 尤其是在没有优化的调试模式下(未完成内联优化),这是一个非常有限的值。 Having a thread with separate stack for every Paralllel.For() statement might help. 为每个Paralllel.For()语句使用一个具有单独堆栈的线程可能会有所帮助。

  2. Use a iteration look instead of recursion to handle the stack depth by yourself. 使用迭代外观而不是递归来自己处理堆栈深度。

I personally would go with option 1. (with or without separate stack) only in case I known the maximum depth of my recursion. 我个人会选择选项1(带或不带单独的堆栈),前提是我知道递归的最大深度。

My preferred solution in most cases like yours will be the iteration approach. 在像您这样的大多数情况下,我首选的解决方案是迭代方法。

Edit by @LordXaosa: I tried this, and all works fine @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);

But there also a limit... 50x50x50 array works fine, but more - stack overflow... In original game it can process 1000x1000x1000 sets, so may be there is another algorithm. 但是也有一个限制... 50x50x50数组可以正常工作,但是更多-堆栈溢出...在原始游戏中,它可以处理1000x1000x1000套,因此可能还有另一种算法。 Thanks for your help! 谢谢你的帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM