не хватает одного параметра: static void Main(string[] args) |
Распараллеливание циклов. Класс Parallel
Вычисление интеграла и Parallel.For
Проведем еще один заключительный эксперимент эффективности применения Parallel.For в сравнении с другими методами распараллеливания. Для этого вернемся к уже хорошо знакомой задаче вычисления определенного интеграла, рассмотренной в предыдущих главах. В "Параллельные алгоритмы" мы начали проектировать класс NewIntegral, добавляя в него в последующих главах различные методы, позволяющие вычислить интеграл. Добавим еще один метод, в котором для распараллеливания циклов используется вызов Parallel.For:
public void ParallelIntegralWithParallelFor() { Parallel.For(0, p, EvalIntegral); result = 0; for (int i = 0; i < p; i++) { result += results[i]; } }
Это самое простое и самое понятное решение. Цикл состоит из одной строчки. В цикле вызывается метод EvalIntegral, которому передается индекс итерации цикла. В методе EvalIntegral формируются границы интервала интегрирования, и вызывается последовательный метод, осуществляющий интегрирование на заданном отрезке. Вот код этого метода:
void EvalIntegral(int i) { double dx = (b - a) / p; double start, finish; start = a + i * dx; finish = start + dx; DefiniteIntegral(start, finish, out results[i]); }
Работа с классом NewIntegral, вызов различных методов этого класса, оценка времени, необходимого для вычислений тем или иным методом, осуществляется в интерфейсном проекте, представляющем традиционный Windows Forms проект. Не буду останавливаться подробно на его описании. На следующем рисунке показан вид этого проекта, содержащего результаты работы каждого из рассматриваемых методов и то время, которое каждый из методов затратил на вычисление интеграла:
В этом эксперименте все параллельные методы в 4-5 раз эффективнее последовательных методов. Лучший результат в это раз показал метод, использующий потоки. Заметьте, при сравнительно большом числе потоков этот метод работает хорошо при длинных итерациях, как в данном случае. Тем не менее, для распараллеливания циклов следует применять метод Parallel.For как наиболее простой, интуитивно понятный метод, соответствующий привычному для нас оператору for.
Цикл while и Parallel.For
До сих пор, говоря о распараллеливании циклов, мы рассматривали исключительно цикл типа for. Более общей формой цикла является форма с циклом while:
while (B) { body }
Как распараллелить такой цикл, когда заголовок цикла не определяет число итераций, требуемых для завершения цикла? Пример на эту тему у нас уже встречался, когда мы рассматривали числа-градины. Там же, по существу, дано и решение возникающей проблемы. Решение основано на возможности использования оператора break в параллельно выполняемых итерациях цикла. Условие выхода (B) проверяется в ходе выполнения итерации и при его истинности осуществляется прерывание выполнения исполняемых итераций. При этом обеспечивается возможность выяснения наименьшего индекса итерации, для которого выполняется условие выхода. Подробная семантика процесса прерывания уже описана в этой главе. Давайте рассмотрим схему замены цикла while параллельным циклом Parallel.For. Она выглядит следующим образом:
ParallelLoopResult res; //Параллельный запуск итераций res = Parallel.For(0, N, body); //минимальный индекс итерации, на которой выполняется условие завершения int index = res.LowestBreakIteration; if (res.IsCompleted) //выход по достижению максимума итераций else //выход по условию цикла while
Тело цикла оформляется как метод, которому передаются два параметра - индекс текущей итерации и параметр класса ParallelLoopState:
void body(int i, ParallelLoopState pls) { //начальная часть тела цикла … //проверка условия выхода if (B) pls.Break(); //завершающая часть тела цикла … }
Остается вопрос, требующий решения, - как задать параметр N. Чаще всего, известно максимально возможное число итераций. Например, в классической задаче поиска по образцу, использующей цикл while, число итераций не может превышать числа элементов массива. В других задачах это число выбирается из каких-то внешних предположений, как например в задаче о числах-градинах, нас интересовал ответ для некоторого фиксированного интервала чисел. Например, для плохо сходящихся процессов разумно задавать некоторое максимальное число итераций, при достижении которого процесс прекращается. Так что число N в этой схеме - это число, ограничивающее максимальное число итераций. Наша схема всегда позволяет выяснить, как закончился цикл - либо по достижению максимума итераций, либо по достижению истинности условия B на итерации. В последнем случае известен минимальный индекс итерации, на котором это условие стало истинным.
Подводя итог, можно сказать, что циклы while также могут быть достаточно просто распараллелены. Как всегда, главная проблема состоит в обеспечении независимости итераций цикла. Ответственность за решение этой проблемы несет программист.