Как изменить синтаксис цикла For на PLINQ?

У меня есть реальная проблема с моим кодом, который очень медленный и делает его бесполезным. Я должен предоставить свои результаты на следующей неделе, а по этому коду это занимает более 4 недель! Поскольку этот код вычисляет движение жидкости, мы имеем дело с более чем 1 миллионом ячеек. Поэтому мой основной цикл For (порядок важен) требует больших вычислений, и прямо сейчас каждый вывод занимает 12 минут. Представьте, мне нужно как минимум 5000 вывода!

Может ли кто-нибудь помочь мне улучшить скорость вычислений? или Не могли бы вы помочь мне понять, как изменить синтаксис цикла For на PLINQ?

Я расстроен

//implementation of time nt = 1,000,000
    For (int t=0; t<nt; t++)
    { 
// main calculations, 2 dimensional   
    for (int i = 0; i < nx; i++)
        {
            for (int j = 0; j < ny; j++)
            {
                if (!Cells[i, j, 0].IsVirtual)
                {
                    // calculate x velocity - nx = 1,000,000 & ny = 1,000,000
                    // calculate y velocity
                    // Data for each (i,j) affect (i+1,j+1)
            }
         }
     }
    }

person user3803849    schedule 04.07.2014    source источник
comment
Первый вопрос будет заключаться в том, можно ли выполнять вычисления параллельно, поскольку вы упоминаете, что порядок важен, поэтому влияет ли один прогон цикла на следующий прогон? Кроме того, где тратится большая часть времени, фактический расчет или вы получаете доступ к объектам для выполнения расчетов?   -  person 3dd    schedule 04.07.2014
comment
Код очень большой, но большая часть вычислений выполняется в двух циклах For и для каждого вывода (у него есть больший цикл For, который работает за раз) должны быть выполнены эти два внутренних цикла For. Эти циклы For вычисляют скорости для 1 миллиона точек. Другие циклы For не влияют на скорость.   -  person user3803849    schedule 04.07.2014
comment
Есть ли у вас горячий путь от профилирования вашей текущей реализации? Использование параллельной обработки может дать незначительное ускорение, но оптимизация фактической реализации может привести к дополнительным улучшениям.   -  person GregC    schedule 04.07.2014
comment
Вы уверены, что каждая внутренняя часть независима от других внутренних частей?   -  person GregC    schedule 04.07.2014
comment
Я согласен с @GregC, возможно, проблема в вашем алгоритме, а не в реализации. Вы рассматривали численные методы решения задачи?   -  person 3dd    schedule 04.07.2014
comment
Если этот алгоритм основан на большом количестве вычислений с плавающей запятой, тогда обработка на стороне графического процессора может быть вашим лучшим выбором. Кроме того, при использовании процессоров Intel с Hyper-Threading для достижения наилучшей производительности не запускайте столько вычислений, как HT, а столько, сколько у вас есть ядер.   -  person GregC    schedule 04.07.2014


Ответы (2)


Чтобы иметь возможность использовать PLINQ, вам нужно изменить двумерный цикл for на последовательность элементов, которые можно обработать (например, IEnumerable<T>). Вы можете создать значения x и y как последовательность, используя этот код:

var indices = Enumerable.Range(0, nx).SelectMany(
  _ => Enumerable.Range(0, ny),
 (x, y) => new { X = x, Y = y }
);

Затем вы можете отфильтровать последовательность индексов, используя Where:

indices.Where(index => !Cells[index.X, index.Y, 0].IsVirtual)

Затем вы можете спроецировать индексы для создания результатов, используя Select:

var results = indices
  .Where(...)
  .Select(index => new { index.X, index.Y, Result = ... });

Когда вы преобразовали свой код в эту форму и убедились, что он работает с небольшим набором данных, вы можете использовать PLINQ для параллельного запуска вычислений в Select, вставив AsParallel:

var results = indices
  .Where(...)
  .AsParallel()
  .Select(index => new { index.X, index.Y, Result = ... })
  .ToList();

В своем вопросе вы говорите о важности порядка. Вы можете убедиться, что полученные результаты хранятся в том же порядке, что и входные индексы, используя AsOrdered:

var results = indices
  .Where(...)
  .AsParallel()
  .AsOrdered()
  .Select(index => new { index.X, index.Y, Result = ... })
  .ToList();

Однако «тело» вашего цикла for по-прежнему выполняется параллельно без какой-либо определенной последовательности. Только окончательные результаты упорядочиваются, когда список создается ToList. Если упомянутая вами важность порядка означает, что у вас есть изменяемое состояние, которое используется в теле цикла for, вы не сможете использовать PLINQ.

Такой подход к использованию PLINQ не особенно эффективен. Если вам нужно обработать 1 000 000 ячеек, этот код выделит 1 000 000 объектов для хранения индексов для ячеек. Однако кажется, что время обработки, а не выделение памяти и сборка мусора, ограничивает ваши вычисления.

Вы можете избежать накладных расходов на выделение памяти, если используете что-то вроде Parallel.For, чтобы распараллелить ваши вычисления, но тогда вы будете использовать TPL, а не PLINQ.

person Martin Liversage    schedule 04.07.2014
comment
Спасибо за ваш ответ. Циклы For должны быть в порядке, потому что результаты каждой итерации необходимы для следующей итерации. Поэтому их следует делать по одному. Таким образом, я не могу использовать TPL и PLINQ. так что я должен сделать для увеличения скорости? - person user3803849; 04.07.2014
comment
@ user3803849: Трудно сказать, потому что у меня нет информации о ваших вычислениях. Чтобы распараллелить ваши вычисления, вам нужно логически найти способ разделить работу, чтобы несколько потоков могли выполнять ее параллельно. TPL и PLINQ могут помочь вам в выполнении работы, только если вы придумали способ ее разделения. Он не может волшебным образом придумать этот раздел для вас. Если вы понимаете, что ваш алгоритм требует, чтобы ячейки обрабатывались в строгой последовательности, вы можете вместо этого сосредоточиться на оптимизации вычислений внутри цикла for. - person Martin Liversage; 04.07.2014
comment
мой расчет заключается в том, что, например, скорость в (i, j) должна вычисляться для скорости в (i + 1, j + 1). Так что все должно быть в порядке. Мой код требует времени, потому что ему нужно вычислить очень много очков и так много времени. Например, не менее 10 миллионов раз для системы с 1 миллионом ячеек (двумерные ячейки). Как я могу оптимизировать такие вычисления? Некоторые из моих друзей, которые пишут на C++, используют реализацию GPU для ускорения своих вычислений. - person user3803849; 04.07.2014
comment
Если вам требуется, чтобы строки вычислялись последовательно, вы можете использовать обычный цикл for для строк внутри этого цикла, у вас может быть параллельный цикл с использованием TPL или PLINQ для ячеек в строке. Поскольку количество ячеек в строке намного больше количества ядер, вы все равно получите максимальное использование своих ядер. - person Martin Liversage; 04.07.2014

используйте приведенный ниже код, вы можете использовать свою коллекцию вместо списка. Дайте мне знать, если вам нужна помощь:

List<string> lst = new List<string>();
foreach (var item in lst.AsParallel())
{
    foreach (var data in item)
    {

    }
}
person Abhishek P    schedule 04.07.2014
comment
Привет, на самом деле, я не мог понять твой комментарий - person user3803849; 04.07.2014
comment
что такое nx в вашем коде или дайте мне весь код, я конвертирую его в plinq. - person Abhishek P; 04.07.2014
comment
Основная часть моего кода, которая имеет самую медленную скорость, — это упомянутые два цикла For. nx= 1000,000 - ny=1000,000 и в цикле For есть большая часть вычислений скорости. - person user3803849; 04.07.2014