Threadpool в C# слишком медленный, есть ли способ ускорить его? Проблемы с Thread.Sleep(0) и QueueUserWorkItem

Я использую Threadpool в C# -приложении, которое должно выполнять некоторую нагрузку на процессор. Кстати, он кажется слишком медленным (РЕДАКТИРОВАТЬ: он печатает строку отладки "Calculating on " + lSubArea.X + ":" + lSubArea.Y + " " + lSubArea.Width + ":" + lSubArea.Height только несколько раз каждые 10 секунд, хотя я ожидаю увидеть, что по крайней мере NUM_ROWS_GRID^2 = 16 раз каждые несколько секунд), также изменяя MinThreads через SetMinThreads метод. Я не знаю, переходить ли на пользовательские темы или есть ли способ ускорить его. Поиск в Google возвращает мне некоторый результат, но ничего не работает; такая же ситуация с MSDN.

Старый Кодекс следует:

private void StreamerRoutine()
{
   if (this._state.Area.Width == 0 && this._state.Area.Height == 0)
      this._state.Area = new Rectangle(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

   while (this._state.WorkEnd == false)
   {
      // Ends time slice if video is off
      if (this._state.VideoOn == false)
         Thread.Sleep(0);
      else
      {
         lock(this._state.AreaSync)
         {
             Int32 lWidth = this._state.Area.Width / Constants.NUM_ROWS_GRID;
             Int32 lHeight = this._state.Area.Height / Constants.NUM_ROWS_GRID;
             for (Int32 lX = 0; lX + lWidth <= this._state.Area.Width; lX += lWidth)
                for (Int32 lY = 0; lY + lHeight <= this._state.Area.Height; lY += lHeight)
                   ThreadPool.QueueUserWorkItem(CreateDiffFrame, (Object)new Rectangle(lX, lY, lWidth, lHeight));
         }
      }
    }
}

private void CreateDiffFrame(Object pState)
{
   Rectangle lSubArea = (Rectangle)pState;

   SmartDebug.DWL("Calculating on " 
          + lSubArea.X + ":" + lSubArea.Y + " " 
          + lSubArea.Width + ":" + lSubArea.Height);
   // TODO : calculate frame
   Thread.Sleep(0);
}

РЕДАКТИРОВАТЬ: функция CreateDiffFrame является только заглушкой, которую я использовал, чтобы знать, сколько раз она вызывается в секунду. Он будет заменен на интенсивную работу процессора, так как в этом случае я определю лучший способ использования потоков.

РЕДАКТИРОВАТЬ: я удалил все Thread.Sleep(0); Я думал, что это может быть способ ускорить рутину, но кажется, что это может быть узким местом... новый код следует:

РЕДАКТИРОВАТЬ: я сделал WorkEnd и VideoOn энергозависимыми, чтобы избежать кэшированных значений и так бесконечный цикл; Я добавил также семафор, чтобы каждая куча рабочих элементов запускалась после того, как предыдущая куча сделана... теперь она работает довольно хорошо

private void StreamerRoutine()
    {
        if (this._state.Area.Width == 0 && this._state.Area.Height == 0)
            this._state.Area = new Rectangle(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

        this._state.StreamingSem = new Semaphore(Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID, Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID);


        while (this._state.WorkEnd == false)
        {
            if (this._state.VideoOn == true)
            {
                for (int i = 0; i < Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID; i++)
                    this._state.StreamingSem.WaitOne();

                lock(this._state.AreaSync)
                {
                    Int32 lWidth = this._state.Area.Width / Constants.NUM_ROWS_GRID;
                    Int32 lHeight = this._state.Area.Height / Constants.NUM_ROWS_GRID;
                    for (Int32 lX = 0; lX + lWidth <= this._state.Area.Width; lX += lWidth)
                        for (Int32 lY = 0; lY + lHeight <= this._state.Area.Height; lY += lHeight)
                            ThreadPool.QueueUserWorkItem(CreateDiffFrame, (Object)new Rectangle(lX, lY, lWidth, lHeight));

                }
            }
        }
    }

private void CreateDiffFrame(Object pState)
    {
        Rectangle lSubArea = (Rectangle)pState;

        SmartDebug.DWL("Calculating on " + lSubArea.X + ":" + lSubArea.Y + " " + lSubArea.Width + ":" + lSubArea.Height);
        // TODO : calculate frame
        this._state.StreamingSem.Release(1);

    }

3 ответа

Решение

На самом деле нет хорошего способа точно сказать вам, что делает ваш код медленным по сравнению с тем, что я вижу, но есть несколько вещей, которые выделяются:

  1. Thread.Sleep (0). Когда вы делаете это, вы отказываетесь от остальной части своего временного интервала от ОС и замедляете все, потому что CreateDiffFrame() фактически не может вернуться, пока планировщик ОС не вернется к нему.

  2. Объект приведен к Rectangle, который является структурой. Когда это происходит, вы несете накладные расходы, связанные с боксом, а это не то, что вам нужно для действительно интенсивных вычислительных операций.

  3. Ваши звонки для блокировки (this._state.AreaSync). Возможно, что AreaSync заблокирован где-то еще, и это может замедлить процесс.

  4. Вы можете ставить элементы в очередь слишком детально - если вы ставите в очередь очень маленькие элементы работы, вполне вероятно, что накладные расходы, связанные с размещением этих элементов в очереди по одному, могут быть значительными по сравнению с фактическим объемом выполненной работы. Возможно, вы могли бы также рассмотреть возможность размещения содержимого внутреннего цикла в рабочем элементе в очереди, чтобы сократить эти накладные расходы.

Если это то, что вы пытаетесь сделать для параллельных вычислений, изучали ли вы с помощью PLINQ или другого такого фреймворка?

Я думаю, что это сон в конце CreateDiffFrame. Это означает, что каждый поток остается в живых по крайней мере еще 10 мс, если я правильно помню. Вы, вероятно, сможете выполнить реальную работу менее чем за 10 мс. ThreadPool пытается оптимизировать использование потоков, но я думаю, что он имеет верхний предел общего числа ожидающих потоков. Поэтому, если вы хотите фактически имитировать свою рабочую нагрузку, сделайте жесткий цикл, который ждет, пока не пройдет ожидаемое количество миллисекунд, а не Sleep.

В любом случае, я не думаю, что использование ThreadPool является узким местом, использование другого механизма потоков не ускорит ваш код.

Существует известная ошибка с ThreadPool.SetMinThreads метод, описанный в KB976898:

После использования метода ThreadPool.SetMinThreads в Microsoft .NET Framework 3.5 потоки, поддерживаемые пулом потоков, не работают должным образом

Вы можете скачать исправление для этого поведения здесь.

Другие вопросы по тегам