Повысит ли производительность вызовов БД asyn c внутри Parallel.ForEach l oop производительность? - PullRequest
0 голосов
/ 20 января 2020

При использовании Parallel.ForEach, может ли преобразование каких-либо вызовов БД или Api в асинхронные методы c повысить производительность?

Немного предыстории, в настоящее время у меня есть консольное приложение, которое последовательно перебирает кучу файлов и для каждого вызывает API и делает несколько вызовов БД. Основной лог c выглядит следующим образом:

foreach (file in files)
{
    ReadTheFileAndComputeAFewThings(file);
    CallAWebService(file);
    MakeAFewDbCalls(file);
}

В настоящее время все вызовы БД и веб-службы являются синхронными.

Изменение l oop на использование Parallel.ForEach дало мне огромное увеличение производительности, как и следовало ожидать.

Мне интересно, сохранил ли я там вызов Parallel.ForEach, а внутри l oop изменил все вызовы веб-службы на asyn c (например, HttpClient.SendAsync) и вызовы БД асинхронные c (с использованием Dapper, db.ExecuteAsync()) - увеличит ли это производительность приложения, позволив ему повторно использовать потоки? Или это ничего не даст, так как Parallel.ForEach все равно заботится о распределении потоков?

Ответы [ 3 ]

2 голосов
/ 20 января 2020

Ответ Нет . Асинхронность обеспечивает масштабируемость, не производительность . Это позволяет выполнять ту же работу с меньшим объемом памяти (каждый заблокированный поток = 1 МБ потерянной памяти).

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

Класс Parallel предназначен для заданий с привязкой к ЦП. Для заданий, которые частично или исключительно связаны с вводом-выводом, предпочтительно использовать асинхронные API-интерфейсы и в идеале обрабатывать связанные с процессором и связанные с вводом-выводом части независимо, поскольку их оптимальные уровни параллелизма обычно различаются. Почти идеальным инструментом для такой работы является библиотека TPL Dataflow . Вы можете создать конвейер блоков потоков данных, связанных друг с другом, и каждый блок может быть настроен с различными MaxDegreeOfParallelism. Для связанных с процессором частей вы ограничены числом процессоров / ядер компьютера, на котором выполняется ваше приложение. Для частей, связанных с вводом / выводом, вы ограничены возможностями удаленного веб-сервера, диска или сервера базы данных.

1 голос
/ 20 января 2020

Parallel.ForEach работает с задачами, а не с потоками. Это означает, что он может порождать больше задач, чем у вас есть потоки в пуле потоков. В этом сценарии использование асинхронных c методов позволяет оптимизировать производительность, выполняя все задачи с меньшим количеством потоков.

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=netcore-3.1

Parallel.ForEach Метод может использовать больше задач, чем потоков за время его выполнения, так как существующие задачи завершаются и заменяются новыми задачами. Это дает базовому объекту TaskScheduler возможность добавлять, изменять или удалять потоки, обслуживающие l oop.

0 голосов
/ 20 января 2020

оригинал

foreach (file in files)
{
    ReadTheFileAndComputeAFewThings(file);
    CallAWebService(file);
    MakeAFewDbCalls(file);
}

оригинал + асин c (лучше, чем выше, зависит!)

foreach (file in files)
{
   await ReadTheFileAndComputeAFewThings(file);
   await CallAWebService(file);
   await MakeAFewDbCalls(file);
}

Это не будет лучше, если вызовы на самом деле не будут реализуя асин c, тогда будет хуже. Еще один способ, которым это будет хуже, - если асин c -несс так короток, что он перевесит стоимость задачи. Каждый asyn c Task создает управляемый поток, который возвращает 1 МБ из системы и добавляет время синхронизации потока. Несмотря на то, что синхронизация крайне мала, если это сделать в сжатые сроки, l oop будет видеть проблемы с производительностью.

Ключом здесь является то, что Задача должна быть на самом деле асинхронной c версии.

  • SaveChanges vs SaveChangesAsyn c

  • Чтение vs ReadAsyn c


Параллельно (лучше, чем выше, зависит!)

Parallel.ForEach(files, item) 
{
    ReadTheFileAndComputeAFewThings(item);
    CallAWebService(item);
    MakeAFewDbCalls(item);
}

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

Не лучше, если методы не являются поточно-ориентированными.


Parallel + asyn c (лучше, чем выше, зависит!)

Parallel.ForEach(files, item) 
{
   await ReadTheFileAndComputeAFewThings(item);
   await CallAWebService(item);
   await MakeAFewDbCalls(item);
}

FYI - Parallel + asyn c пример выше на самом деле неверен !!! Так как сам Parallel.ForEach не является асинхронным c, вам нужно будет провести некоторое исследование относительно того, как построить асинхронную c версию Parallel.ForEach

Также те же самые комментарии выше применяются при использовании в сочетании.

Обновление

на основе комментария, это в значительной степени зависит от того, был ли установлен ConfigureAwait (), но при условии, что вы этого не сделали. Кроме того, это не будет оправданием, так что если CallAWebService зависит от ReadTheFileAndComputeAFewThings, то, вероятно, что-то пойдет не так.

foreach (file in files)
{
   List<Task> jobs = new List<Task>();
   jobs.Add(ReadTheFileAndComputeAFewThings(file))
   jobs.Add(CallAWebService(file))
   jobs.Add(MakeAFewDbCalls(file))
   Task.WhenAll(jobs.ToArray());
}

или ...

 List<Task> jobs = new List<Task>();
foreach (file in files)
{
   jobs .Add(ReadTheFileAndComputeAFewThings(file))
   jobs .Add(CallAWebService(file))
   jobs .Add(MakeAFewDbCalls(file))
}
Task.WhenAll(jobs.ToArray());

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

Удивительная ссылка объяснение асин c ... https://docs.microsoft.com/en-us/archive/blogs/benwilli/tasks-are-still-not-threads-and-async-is-not-parallel

...