2016-03-31 2 views
-2
private void GenerateRecords(JobRequest request) 
{ 
    for (var day = 0; day < daysInRange; day++) 
    { 
      foreach (var coreId in request.CoreIds) 
      { 
       foreach (var agentId in Enumerable.Range(0, request.AgentsCount).Select(x => Guid.NewGuid())) 
       { 
        for (var copiesDone = 0; copiesDone < request.CopiesToMake; copiesDone++) 
        { 
         foreach (var jobInfoRecord in request.Jobs) 
         { 
          foreach (var status in request.Statuses) 
          { 
            //DoSomeWork(); 
          } 
         } 
        } 
       } 
      } 
    } 
} 

Есть ли способ увеличить производительность многих итераций? Мне действительно нужно иметь все эти циклы, но мне было интересно, как я могу улучшить (ускорение) итераций? Может быть, используя linq?Как повысить производительность многих циклов foreach?

+0

Зависит, что вы делаете со значениями каждого 'foreach'? –

+0

Ничего, просто создавайте новые объекты – Anatoly

+0

Если проблема связана с петлями, вы можете попробовать использовать разворот цикла. Или у вас всегда есть возможность использовать потоки. – Carra

ответ

4

Как только вы окажетесь на LINQ, вы находитесь на милость своих счетчиков и сборщиков мусора.

Если вы хотите выжать максимум производительности, насколько это возможно из ваших foreach петель и у вас есть контроль над структурами данных, убедитесь, что вы используете типы коллекций, которые имеют структуры счетчиков (т.е. List<T>, ImmutableArray<T>). Еще лучше, если это возможно, используйте простые общие массивы. Несмотря на неструктурный перечислитель, они являются самым быстрым типом коллекции в .NET, когда дело доходит до необработанной скорости доступа, по крайней мере, при создании в Release/с включенными оптимизациями (в этом случае компилятор испускает один и тот же IL для ваших циклов по массивам как это было бы для циклов for, тем самым сокращая ассигнования и вызовы методов, обычно связанные с использованием типов, которые реализуют IEnumerable<T>).

Рослин имеет set of guidelines для горячих путей кода, которые полезны в вашей ситуации:

  • Избегайте LINQ.
  • Избегайте использования foreach над коллекциями, которые не имеют перечислителя структуры.

Теперь приведенное выше действие действительно в любом критическом сценарии. Тем не менее, я скептически отношусь к тому, что производительность итераций сборов является узким местом в вашей конкретной ситуации. Скорее всего, DoSomeWork занимает больше времени, чем вам хотелось бы. Вы должны прокомментировать ваш вызов GenerateRecords, чтобы получить окончательный ответ о том, какие биты кода требуют наибольшего внимания.

Если вы уверены, что ваша реализация DoSomeWork оптимальна, рассмотрите возможность параллелизации вашей рабочей нагрузки.

При условии, что ваш DoSomeWork реализация является чистой и не зависит от внешних изменяемых состояний (то есть переменный класс), вы можете быть в состоянии parallelise некоторые из ваших итераций цикла через Parallel.For или Parallel.ForEach. Ваш внешний контур выглядит для него особенно хорошим кандидатом, но вам, возможно, придется поиграть с размещением параллельного цикла, пока не получите желаемые характеристики производительности. В качестве отправной точки, вот что я бы рекомендовал:

private void GenerateRecords(JobRequest request) 
{ 
    Parallel.For(0, daysInRange, day => 
    { 
     foreach (var coreId in request.CoreIds) 
     { 
      for (var i = 0; i < request.AgentsCount; i++) 
      { 
       var agentId = Guid.NewGuid(); 

       for (var copiesDone = 0; copiesDone < request.CopiesToMake; copiesDone++) 
       { 
        foreach (var jobInfoRecord in request.Jobs) 
        { 
         foreach (var status in request.Statuses) 
         { 
          //DoSomeWork(); 
         } 
        } 
       } 
      } 
     } 
    }); 
} 
+0

Мне нравится первая директива Roslyn - простая и понятная :) –

1

Сколько раз вам нужно сделать //DoSomeWork()? Есть ли кто-нибудь из тех, кто потратил впустую? Если нет, петли не имеют значения.

Как вы можете это сказать, запустите его под IDE Visual Studio, и пока он работает, нажмите кнопку «Пауза». Затем отобразите стек вызовов.

Если //DoSomeWork() не принимает несколько инструкций, пауза будет находиться в //DoSomeWork(). Если вы делаете это 10 раз, доля проб, приземляющихся в этой функции, примерно равна половине времени, которое она проводит. Если 8 из 10 образцов попадают в эту функцию, и 2 из них попадают в петли (скорее всего, самый внутренний цикл), то даже если вы развернули цикл или уменьшили его стоимость до 0, вы не сэкономили бы больше, чем примерно 20%.

Все, что стоит потратить, - это то, что стоит больше всего.

1

Это правда, что LINQ не ускоряет ничего по сравнению со «старой школой» для петель. Вместо этого LINQ обычно имеет небольшую стоимость. Однако, в зависимости от вашей проблемы (которую вы должны измерить, чтобы лучше понять ее), вы можете значительно повысить производительность, распараллеливая свое решение. И здесь LINQ может пригодиться.

Вы можете изменить все для петель в один IEnumerable<T> создан с помощью LINQ:

var query = from day in Enumerable.Range(0, request.daysInRange) 
      from coreId in request.CoreIds 
      from agentId in Enumerable.Range(0, request.AgentsCount).Select(x => Guid.NewGuid()) 
      from copiesDone in Enumerable.Range(0, request.CopiesToMake) 
      from jobInfoRecord in request.Jobs 
      from status in request.Statuses 
      select new { 
       Day = day, 
       CoreId = coreId, 
       AgentId = agentId, 
       CopiesDone = copiesDone, 
       JobInfoRecord = jobInfoRecord, 
       Status = status 
      }; 

Предполагая, что работа, которую вы делаете в петлю, чтобы создать новый объект, вы можете проецировать элементы с помощью Select:

var results = query.Select(item => /* do some work and return something */); 

Однако, вы можете использовать PLINQ распараллелить код с небольшим изменением, где AsParallel() вставляется:

var results = query.AsParallel().Select(item => /* do some work and return something */); 

Если у вас есть 4-ядерный вычислитель, вы можете ожидать почти 4-кратное увеличение скорости, пока выполняемая работа связана с ЦП.

Отладка параллельного кода может быть затруднена, но при условии, что любая проблема, которую вы отлаживаете, не связана с параллельным выполнением кода, вы можете легко отключить распараллеливание, удалив .AsParallel(). Это может быть очень удобно.

+0

Итерация над 'query.AsParallel()' results в * отсутствие * распараллеливания в отсутствие других вызовов метода PLINQ ('Select',' Where' и т. д. - которых в этом случае нет). Как только вы вызываете 'GetEnumerator' на' ParallelQuery ', вы возвращаетесь к последовательной обработке - и в вашем примере это происходит немедленно. –

+0

Вы * можете *, однако, подключить '.AsParallel()' call (s) в ваш большой запрос LINQ, и в этом случае разрешение перегрузки будет выбирать 'Select',' SelectMany', 'Aggregate',' Where', ' OrderBy' и другие перегрузки, определенные в 'ParallelEnumerable', и все будет работать параллельно. –

+0

@KirillShlenskiy: Спасибо за отзыв. Я обновил свой ответ. –

Смежные вопросы