c#多线程仅使用25%的CPU

时间:2019-09-20 13:42:24

标签: c# parallel-processing

我有一个尝试使用多线程的遗传嵌套算法的过程。该过程如下所示。

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

namespace ConsoleApp1
{
  class Program
  {
    static void Main(string[] args)
    {
      CurrentNest = new CuttingRun();
      for (int i = 0; i < 80; i++)
      {
        double w = GetRandomNumber(24, 50);
        double h = GetRandomNumber(10, 15);
        CurrentNest.PartList.Add(new LwCube { Width = w, Height = h, PartID = i });
      }
      //Task.Run(() =>
      //{
      //  Parallel.For(0, 64, (i) => Parallel_Nest());
      //});
      while (true)
      {
        Parallel_Nest();
      }
      //Console.ReadKey();
    }

    public static double GetRandomNumber(double minimum, double maximum)
    {
      Random random = new Random();
      return random.NextDouble() * (maximum - minimum) + minimum;
    }

    public static CuttingRun CurrentNest { get; set; }
    public static void Parallel_Nest()
    {
      Random random = new Random();
      int randomNumber = random.Next(2000, 10000);     
      var retVal = Nester.Nest_Parts(CurrentNest, randomNumber);
      CurrentNest.Iterations++;
      if (CurrentNest.SavedList.Count > 0)
      {
        if (retVal.Count < CurrentNest.SavedList.Count)
        {
          CurrentNest.SavedList = retVal;          
        }
      }
      else
      {
        CurrentNest.SavedList = retVal;
      }
      Console.WriteLine(CurrentNest.Iterations.ToString() + " " + CurrentNest.SavedList.Count.ToString());
      if (CurrentNest.SavedList != retVal)
      {
        retVal.Clear();
      }
    }    
  }


//Models
public class LwSheet
  {
    public LwSheet(double width, double height)
    {
      SheetWidth = width;
      SheetHeight = height;
      FreeRectangles.Add(new LwCube { Width = width, Height = height, X = 0, Y = 0 });
    }
    public List<LwCube> UsedRectangles = new List<LwCube>();
    public List<LwCube> FreeRectangles = new List<LwCube>();
    public double SheetWidth { get; set; }
    public double SheetHeight { get; set; }
    public double TotalUsed { get; set; }
    public bool Place_Part(LwCube prt)
    {
      bool retVal = false;
      LwCube bestNode = FindPositionForBestAreaFit(prt);
      //if the bestNode has a height then add our parts to the list
      if (bestNode.Height > 0)
      {
        bestNode.PartID = prt.PartID;
        int numRectanglesToProcess = FreeRectangles.Count;
        for (int i = 0; i < numRectanglesToProcess; ++i)
        {
          if (SplitFreeNode(FreeRectangles[i], ref bestNode))
          {
            FreeRectangles.RemoveAt(i);
            --i;
            --numRectanglesToProcess;
          }
        }

        PruneFreeList();
        UsedRectangles.Add(bestNode);
        retVal = true;
      }
      return retVal;
    }
    bool SplitFreeNode(LwCube freeNode, ref LwCube usedNode)
    {
      // Test with SAT if the rectangles even intersect.
      if (usedNode.X >= freeNode.X + freeNode.Width || usedNode.X + usedNode.Width <= freeNode.X ||
        usedNode.Y >= freeNode.Y + freeNode.Height || usedNode.Y + usedNode.Height <= freeNode.Y)
        return false;

      if (usedNode.X < freeNode.X + freeNode.Width && usedNode.X + usedNode.Width > freeNode.X)
      {
        // New node at the top side of the used node.
        if (usedNode.Y > freeNode.Y && usedNode.Y < freeNode.Y + freeNode.Height)
        {
          LwCube newNode = new LwCube { Width = freeNode.Width, X = freeNode.X, Y = freeNode.Y };
          newNode.Height = usedNode.Y - newNode.Y;
          FreeRectangles.Add(newNode);
        }

        // New node at the bottom side of the used node.
        if (usedNode.Y + usedNode.Height < freeNode.Y + freeNode.Height)
        {
          LwCube newNode = new LwCube { Width = freeNode.Width, X = freeNode.X };
          newNode.Y = usedNode.Y + usedNode.Height;
          newNode.Height = freeNode.Y + freeNode.Height - (usedNode.Y + usedNode.Height);
          FreeRectangles.Add(newNode);
        }
      }

      if (usedNode.Y < freeNode.Y + freeNode.Height && usedNode.Y + usedNode.Height > freeNode.Y)
      {
        // New node at the left side of the used node.
        if (usedNode.X > freeNode.X && usedNode.X < freeNode.X + freeNode.Width)
        {
          LwCube newNode = new LwCube { Height = freeNode.Height, X = freeNode.X, Y = freeNode.Y };
          newNode.Width = usedNode.X - newNode.X;
          FreeRectangles.Add(newNode);
        }

        // New node at the right side of the used node.
        if (usedNode.X + usedNode.Width < freeNode.X + freeNode.Width)
        {
          LwCube newNode = new LwCube { Height = freeNode.Height, Y = freeNode.Y };
          newNode.X = usedNode.X + usedNode.Width;
          newNode.Width = freeNode.X + freeNode.Width - (usedNode.X + usedNode.Width);
          FreeRectangles.Add(newNode);
        }
      }

      return true;
    }    
    void PruneFreeList()
    {
      for (int i = 0; i < FreeRectangles.Count; ++i)
        for (int j = i + 1; j < FreeRectangles.Count; ++j)
        {
          if (IsContainedIn(FreeRectangles[i], FreeRectangles[j]))
          {
            FreeRectangles.RemoveAt(i);
            --i;
            break;
          }
          if (IsContainedIn(FreeRectangles[j], FreeRectangles[i]))
          {
            FreeRectangles.RemoveAt(j);
            --j;
          }
        }
    }
    bool IsContainedIn(LwCube a, LwCube b)
    {
      return a.X >= b.X && a.Y >= b.Y
        && a.X + a.Width <= b.X + b.Width
        && a.Y + a.Height <= b.Y + b.Height;
    }
    LwCube FindPositionForBestAreaFit(LwCube prt)
    {
      LwCube bestNode = new LwCube();
      var bestAreaFit = SheetWidth * SheetHeight;
      for (int i = 0; i < FreeRectangles.Count; ++i)
      {
        double areaFit = FreeRectangles[i].Width * FreeRectangles[i].Height - prt.Width * prt.Height;

        // Try to place the rectangle in upright (non-flipped) orientation.
        if (FreeRectangles[i].Width >= prt.Width && FreeRectangles[i].Height >= prt.Height)
        {
          if (areaFit < bestAreaFit)
          {
            bestNode.X = FreeRectangles[i].X;
            bestNode.Y = FreeRectangles[i].Y;
            bestNode.Height = prt.Height;
            bestNode.Width = prt.Width;
            bestAreaFit = areaFit;
          }
        }
      }
      return bestNode;
    }   
  }

  public class LwCube
  {
    public int PartID { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public double X { get; set; }
    public double Y { get; set; }
  }

  public class CuttingRun
  {    
    public List<LwCube> PartList = new List<LwCube>();
    public List<LwSheet> SavedList = new List<LwSheet>();
    public List<LwSheet> Sheets = new List<LwSheet>();
    public int Iterations { get; set; }
  }


//Actions
public static class Nester
  {
    public static List<LwSheet> Nest_Parts(CuttingRun cuttingRun, int loopCount)
    {
      var SheetList = new List<LwSheet>();
      List<LwCube> partList = new List<LwCube>();
      partList.AddRange(cuttingRun.PartList);
      while (partList.Count > 0)
      {
        LwSheet newScore = new LwSheet(97, 49);
        List<LwCube> addingParts = new List<LwCube>();
        foreach (var prt in partList)
        {
          addingParts.Add(new LwCube { Width = prt.Width, Height = prt.Height, PartID = prt.PartID });
        }
        if (addingParts.Count > 0)
        {
          var sheets = new ConcurrentBag<LwSheet>();
          Parallel.For(0, loopCount, (i) =>
          {
            var hmr = new LwSheet(97, 49);
            Add_Parts_To_Sheet(hmr, addingParts);
            sheets.Add(hmr);
          });
          //for (int i = 0; i < loopCount; i++)
          //{
          //  var hmr = new LwSheet(97, 49);

          //  Add_Parts_To_Sheet(hmr, addingParts, addToLarge, addToMedium);
          //  sheets.Add(hmr);
          //}
          addingParts.Clear();
          var bestSheet = sheets.Where(p => p != null).OrderByDescending(p => p.TotalUsed).First();
          sheets = null;
          newScore = bestSheet;
          foreach (var ur in newScore.UsedRectangles)
          {
            partList.Remove(partList.Single(p => p.PartID == ur.PartID));
          }
          SheetList.Add(newScore);
        }
      }
      return SheetList;
    }       
    public static void Add_Parts_To_Sheet(LwSheet sh, List<LwCube> parts)
    {
      var myList = new List<LwCube>();
      myList.AddRange(parts);
      myList.Shuffle();
      foreach (var prt in myList)
      {
        sh.Place_Part(prt);
      }
      myList.Clear();
      foreach (var ur in sh.UsedRectangles)
      {
        sh.TotalUsed += ur.Width * ur.Height;
      }
    }

    [ThreadStatic] private static Random Local;
    public static Random ThisThreadsRandom
    {
      get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + System.Threading.Thread.CurrentThread.ManagedThreadId))); }
    }

    public static void Shuffle<T>(this IList<T> list)
    {
      int n = list.Count;
      while (n > 1)
      {
        n--;
        int k = ThisThreadsRandom.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
      }
    }
  }
}

我尝试在每个循环上使用并行的for循环,以尝试加快处理速度。我也尝试过将它们更改为任务,并使用task.WhenAll。但是,我只能使用大约25%的CPU。如果我在4次不同的时间启动程序,则可以使用100%。 我想知道是否有人对我如何使用100%CPU而不多次启动程序有任何想法? 编辑:添加按比例缩小的工作版本后,我还注释掉了并行循环之一和正常循环之一,以显示我将它们放在代码中的位置。

1 个答案:

答案 0 :(得分:2)

  

但是,我只能使用大约25%的CPU。如果我在4次不同的时间启动程序,则可以使用100%。我想知道是否有人对我如何使用100%的CPU而无需多次启动该程序有任何想法?

您的代码似乎是异步部分(可能是I / O绑定)和并行部分(可能是CPU绑定)的混合。我之所以说“似乎是”是因为我们不能确定问题出在哪里,因为这不是minimal reproducible example

但是,如果这个假设是正确的,那么CPU使用率不足的原因很简单:并行CPU绑定部分正在等待异步I / O绑定部分的输入数据。解决此问题的唯一方法是同时运行受I / O约束的部分。尽可能早地在管道中移动与I / O绑定的代码,然后确保尽可能并发地运行与I / O绑定的部分。例如,如果您必须为每个项目调用一个WebApi,请在拥有该项目后立即调用它;或者,如果您要从数据库中读取项目,请尝试批量读取尽可能多的内容。这是为了最大程度地减少CPU绑定部分必须等待其数据的时间。

“异步并行ForEach”很少是解决此类问题的好工具。我要么研究TPL Dataflow,要么使用Channels建立自己的管道。

最终,整个算法可能受I / O约束。在这种情况下,您无能为力:只能使用一个CPU,因为I / O甚至无法跟上单个CPU的速度,在这种情况下,使用更多CPU不会带来任何好处。