在递归算法中使用Parallel.For的最佳方法是什么?

时间:2019-04-30 04:12:10

标签: c#

我正在构建用于评估许多可能解决方案的软件,并试图引入并行处理以加快计算速度。我的第一个尝试是建立一个数据表,每一行都是要评估的解决方案,但是建立数据表需要花费很多时间,而且当可能的解决方案数以百万计时,我遇到了内存问题。

需要这些解决方案的问题的结构如下: 有x个事件的范围日期,必须按顺序进行。评估的解决方案如下所示,每种解决方案都是一行,事件是列,而天数是值。

给出3天(0到2天)和3个事件:
0 0 0
0 0 1
0 0 2
0 1 1
0 1 2
0 2 2
1 1 1
1 1 2
1 2 2
2 2 2

我的新计划是使用递归并在进行过程中评估解决方案,而不是构建解决方案集然后进行评估。


for(int day = 0; day < maxdays; day++)
{
  List<int> mydays = new List<int>();
  mydays.Add(day);
  EvalEvent(0,day,mydays);
}

private void EvalEvent(int eventnum, 
int day, List<int> mydays)
{
   Parallel.For(day,maxdays, day2 =>  
// events must be on same day or after previous events
   {
     List<int> mydays2 = new List<int>();
     for(int a = 0; a <mydays.Count;a++)
     {
        mydays2.Add(mydays[a]);
     }
     mydays2.Add(day2);
     if(eventnum< eventcount - 1) // proceed to next event
     {
      EvalEvent(eventnum+1, day2,mydays2);
     }
     else
     {
       EvalSolution(mydays2);
     }
   });
}

我的问题是,这是否实际上是对并行处理的有效利用,还是会产生太多线程并减慢速度?应该只在eventnum的最后一个值还是最后几个值上执行并行循环,还是有更好的方法来解决该问题?

请求的旧代码几乎如下:

private int daterange;
private int events;
private void ScheduleIt()
{
  daterange = 10;
  events = 6;
  CreateSolutions();
  int best = GetBest();
}
private DataTable Options();
private bool CreateSolutions()
{
   Options= new DataTable();
   Options.Columns.Add();

for (int day1=0;day1<=daterange ;day1++)
{
    Options.Rows.Add(day1);
}

for (int event =1; event<events; event++)
{
    Options.Columns.Add();

    foreach(DataRow dr in Options.Rows)
    {dr[Options.Columns.Count-1] = dr[Options.Columns.Count-2] ;}
    int rows = Options.Rows.Count;
    for (int day1=1;day1<=daterange ;day1++)
    {

        for(int i = 0; i <rows; i++)
        {
            if(day1 > Convert.ToInt32(Options.Rows[i][Options.Columns.Count-2]))
            {
                try{
                Options.Rows.Add();
                for (int col=0;col<Options.Columns.Count-1;col++)
                    {

                        Options.Rows[Options.Rows.Count-1][col] =Options.Rows[i][col];

                    }
                Options.Rows[Options.Rows.Count-1][Options.Columns.Count-1] = day1;

                }
                catch(Exception ex)
                {
                 return false;
                }
            }
        }
    }
}
return true;

}
private intGetBest()
{
    int bestopt = 0;
    double bestscore =999999999;


    Parallel.For(  0,  Options.Rows.Count,opt => 
    {

        double score = 0;

        for(int i = 0; i <Options.Columns.Count;i++)
        {score += Options.Rows[opt][i]}// just a stand in calc for a score
        if (score < bestscore)
        {bestscore = score;
            bestopt = opt;
        }

    });
 return bestopt;

}

2 个答案:

答案 0 :(得分:1)

即使完成没有错误,也无法显着加快解决方案的速度。

似乎每个递归级别都尝试开始多个(例如最多说“ k”)下一级别的调用,让我们称之为“ n”级别。这实质上意味着代码是O(k ^ n),它增长非常快。这样的O(k ^ n)解决方案的非算法加速本质上是没有用的(除非k和n都非常小)。特别是,并行运行代码只会给您带来恒定的速度提升(CPU支持的线程数量大约),而这根本不会改变复杂性。

实际上,对新线程的请求创建成指数级增长,仅管理线程本身可能会引起更多问题。

除了不能显着提高性能之外,并行代码更难编写,因为它需要适当的同步或切割器数据分区-在您的情况下似乎都不存在。

答案 1 :(得分:0)

当工作量很大且平衡时,并行化效果最佳。理想情况下,您希望将工作划分为与计算机逻辑处理器一样多的独立分区,以确保所有分区的大小大致相同。这样,所有可用的处理器将在大约相同的持续时间内以最大的效率工作,并且您将在最短的时间内得到结果。

当然,您应该从一个无错误的串行实现开始,然后考虑对工作进行分区的方法。最简单的方法通常不是最佳方法。例如,一个简单的方法是将您的工作转换为LINQ查询,然后将其与AsParallel()并行化(使其成为PLINQ)。这通常会导致分区过于精细,从而导致过多的开销。如果您找不到改善的方法,则可以采用Parallel.ForParallel.ForEach的方法,它们要复杂一些。

LINQ实现可能应该从创建一个迭代器开始,该迭代器将生成您所有的工作单元(事件或解决方案,对我来说不是很清楚)。

public static IEnumerable<Solution> GetAllSolutions()
{
    for (int day = 0; day < 3; day++)
    {
        for (int ev = 0; ev < 3; ev++)
        {
            yield return new Solution(); // ???
        }
    }
}

如果您创建了具体的类来表示您要处理的实体,那肯定会有所帮助。