Параллельное выполнение задач для эффективного использования вычислительных узлов пакетной службы

Можно максимизировать использование ресурсов при меньшем количестве вычислительных узлов в пуле, выполняя несколько задач одновременно на каждом узле.

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

  • Минимизация передачи данных для задач, способных совместно использовать данные. Можно существенно сократить стоимость передачи данных, копируя общие данные на меньшее количество узлов, а затем выполняя задачи в параллельном режиме на каждом узле. Это особенно актуально, если данные, которые копируются на каждый узел, необходимо передавать между географическими регионами.
  • Максимизация использования памяти для задач, которым требуется большой объем памяти, но только в течение короткого времени, а также с переменной периодичностью во время выполнения. Для эффективной обработки таких нагрузок можно использовать меньшее число более крупных вычислительных узлов с большим объемом памяти. Эти узлы будут содержать параллельные задачи, которые выполняются на каждом узле, но каждая задача может использовать большой объем памяти узлов в разное время.
  • Уменьшение ограничений по количеству узлов, когда требуется обмен данными между узлами в пределах пула. Сейчас существует ограничение в 50 вычислительных узлов для пулов, в которых настроен обмен данными между узлами. Поэтому если каждый узел в таком пуле будет выполнять задачи параллельно, можно будет выполнять больше задач одновременно.
  • Репликация локального вычислительного кластера, например, как при первом переходе в вычислительную среду Azure. Увеличение максимального числа задач на узле позволит точнее скопировать существующую физическую конфигурацию, если текущее локальное решение позволяет выполнять несколько задач на каждом вычислительном узле.

Пример сценария

В качестве примера представьте себе приложение для задачи с требованиями к ЦП и памяти, которого достаточно узлов категории Standard_D1. Однако для выполнения задачи в заданное время потребуется 1000 таких узлов.

Вместо узлов размера Standard_D1, каждый из которых содержит одно ядро ЦП, вы можете использовать 16-ядерные узлы Standard_D14, включив на них параллельное выполнение задач. Это значит, что потребуется в 16 раз меньше узлов, то есть 63 узла вместо 1000. Если каждому узлу требуются большие файлы приложений либо эталонные данные, время выполнения и эффективность улучшатся, так как данные будут копироваться только на 63 узла.

Включение параллельного выполнения задач

Настройка параллельного выполнения задач для вычислительных узлов выполняется на уровне пула. С помощью библиотеки .NET пакетной службы задайте свойство CloudPool.TaskSlotsPerNode при создании пула. Если вы используете REST API пакетной службы, включите элемент taskSlotsPerNode в текст запроса при создании пула.

Примечание

Элемент taskSlotsPerNode и свойство TaskSlotsPerNode можно задать только во время создания пула. Их невозможно изменить после создания пула.

Пакетная служба Azure позволяет задать для каждого узла количество слотов задач, в 4 раза превышающее количество ядер. Например, если для пула настроены узлы размера "Большой" (четыре ядра), для параметра taskSlotsPerNode можно задать значение 16. Но независимо от количества ядер узла, количество слотов задач на каждом узле не может превышать 256. Сведения о количестве ядер для каждого узла см. в статье Размеры для облачных служб. Дополнительные сведения об ограничениях службы см. в статье Квоты и ограничения пакетной службы Azure.

Совет

Обязательно учитывайте значение параметра taskSlotsPerNode при создании формулы автомасштабирования для пула. Например, если в формуле учитывается параметр $RunningTasks, изменение количества задач существенно повлияет на результат ее применения. Дополнительные сведения см. в статье Автоматическое масштабирование вычислительных узлов в пуле пакетной службы Azure.

Указание распределения задач

При включении параллельных задач важно указать способ распределения задач по узлам в пуле.

С помощью свойства CloudPool.TaskSchedulingPolicy можно указать, что задачи должны назначаться ("распространяться") равномерно по всем узлам в кластере. Или можно указать, что перед переходом к следующему узлу необходимо назначить максимальное количество задач текущему узлу ("упаковка").

В качестве примера рассмотрим пул из узлов размера Standard_D14 (в приведенном выше примере), для которых параметр CloudPool.TaskSlotsPerNode имеет значение 16. Если в свойстве CloudPool.TaskSchedulingPolicy для параметра ComputeNodeFillType указано значение Pack, то на каждом узле все 16 ядер будут задействованы по максимуму. При этом автомасштабируемый пул будет исключать из пула неиспользуемые узлы (для которых не назначены задачи). Это снизит использование ресурсов и расходы на них.

Определение слотов переменных для задачи

Задачу можно определить с помощью свойства CloudTask.RequiredSlots, указав, сколько слотов требуется для ее выполнения на вычислительном узле. Значение по умолчанию — 1. Можно задать переменные слоты задач, если задачи имеют разные весовые коэффициенты в отношении использования ресурсов на вычислительном узле. В результате каждый вычислительный узел будет иметь разумное количество параллельно выполняемых задач без переполнения системных ресурсов, таких как ЦП или память.

Например, для пула со свойством taskSlotsPerNode = 8 можно отправить многоядерные задачи, требующие интенсивного использования ЦП, с помощью requiredSlots = 8, а для других задач можно задать значение requiredSlots = 1. При планировании этой смешанной рабочей нагрузки задачи с интенсивным использованием ЦП будут выполняться исключительно на своих вычислительных узлах, а другие задачи могут выполняться одновременно (до восьми задач одновременно) на других узлах. Это помогает сбалансировать рабочую нагрузку на разных вычислительных узлах и повысить эффективность использования ресурсов.

Убедитесь, что не указано, чтобы requiredSlots для задачи превышало значение taskSlotsPerNode для пула. Это приведет к тому, что задача никогда не сможет выполняться. Пакетная служба в настоящее время не проверяет этот конфликт при отправке задач, так как в задании может отсутствовать привязанный пул во время отправки или он может быть изменен на другой пул путем отключения или повторного включения.

Совет

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

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

Пример использования компонента .NET пакетной службы

В следующих фрагментах кода API .NET для пакетной службы показано, как создать пул с несколькими слотами задач на узел и как отправить задачу с нужными слотами.

Создание пула с несколькими слотами задач на узел

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

Дополнительные сведения о добавлении пулов с использованием API .NET пакетной службы см. в описании метода BatchClient.PoolOperations.CreatePool.

CloudPool pool =
    batchClient.PoolOperations.CreatePool(
        poolId: "mypool",
        targetDedicatedComputeNodes: 4
        virtualMachineSize: "standard_d1_v2",
        VirtualMachineConfiguration: new VirtualMachineConfiguration(
            imageReference: new ImageReference(
                                publisher: "MicrosoftWindowsServer",
                                offer: "WindowsServer",
                                sku: "2019-datacenter-core",
                                version: "latest"),
            nodeAgentSkuId: "batch.node.windows amd64");

pool.TaskSlotsPerNode = 4;
pool.TaskSchedulingPolicy = new TaskSchedulingPolicy(ComputeNodeFillType.Pack);
pool.Commit();

Создание задачи с необходимыми слотами

Этот фрагмент кода создает задачу со значением requiredSlots, отличным от значения по умолчанию. Эта задача будет выполняться только при наличии достаточного количества свободных слотов на вычислительном узле.

CloudTask task = new CloudTask(taskId, taskCommandLine)
{
    RequiredSlots = 2
};

Вывод списка вычислительных узлов с учетом количества выполняемых задач и слотов

Этот фрагмент кода перечисляет все вычислительные узлы в пуле и выводит счетчики для выполняемых задач и слотов задач на каждом узле.

ODATADetailLevel nodeDetail = new ODATADetailLevel(selectClause: "id,runningTasksCount,runningTaskSlotsCount");
IPagedEnumerable<ComputeNode> nodes = batchClient.PoolOperations.ListComputeNodes(poolId, nodeDetail);

await nodes.ForEachAsync(node =>
{
    Console.WriteLine(node.Id + " :");
    Console.WriteLine($"RunningTasks = {node.RunningTasksCount}, RunningTaskSlots = {node.RunningTaskSlotsCount}");

}).ConfigureAwait(continueOnCapturedContext: false);

Перечисление количества задач для задания

Этот фрагмент кода возвращает количество задач для задания, которое включает количество как задач, так и слотов задач на каждое состояние задачи.

TaskCountsResult result = await batchClient.JobOperations.GetJobTaskCountsAsync(jobId);

Console.WriteLine("\t\tActive\tRunning\tCompleted");
Console.WriteLine($"TaskCounts:\t{result.TaskCounts.Active}\t{result.TaskCounts.Running}\t{result.TaskCounts.Completed}");
Console.WriteLine($"TaskSlotCounts:\t{result.TaskSlotCounts.Active}\t{result.TaskSlotCounts.Running}\t{result.TaskSlotCounts.Completed}");

Пример интерфейса REST API пакетной службы

В следующих фрагментах кода REST API для пакетной службы показано, как создать пул с несколькими слотами задач на узел и как отправить задачу с нужными слотами.

Создание пула с несколькими слотами задач на узел

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

Дополнительные сведения о добавлении пулов с помощью REST API см. в статье Добавление пула к учетной записи.

{
  "odata.metadata":"https://myaccount.myregion.batch.azure.com/$metadata#pools/@Element",
  "id":"mypool",
  "vmSize":"large",
  "virtualMachineConfiguration": {
    "imageReference": {
      "publisher": "canonical",
      "offer": "ubuntuserver",
      "sku": "18.04-lts"
    },
    "nodeAgentSKUId": "batch.node.ubuntu 16.04"
  },
  "targetDedicatedComputeNodes":2,
  "taskSlotsPerNode":4,
  "enableInterNodeCommunication":true,
}

Создание задачи с необходимыми слотами

В этом фрагменте кода показан запрос на добавление задачи со значением requiredSlots, отличным от значения по умолчанию. Эта задача будет выполняться только при наличии достаточного количества свободных слотов на вычислительном узле.

{
  "id": "taskId",
  "commandLine": "bash -c 'echo hello'",
  "userIdentity": {
    "autoUser": {
      "scope": "task",
      "elevationLevel": "nonadmin"
    }
  },
  "requiredSLots": 2
}

Пример кода на GitHub

Проект ParallelTasks в репозитории GitHub является примером использования свойства CloudPool.TaskSlotsPerNode.

Это консольное приложение C# использует библиотеку .NET пакетной службы для создания пула с одним или несколькими вычислительными узлами. Для имитации переменной нагрузки оно выполняет заданное количество задач на этих узлах. Вывод приложения показывает, на каких узлах выполнялась каждая задача. Приложение также предоставляет сводку параметров задания и времени его выполнения.

Например, ниже приведена сводная часть выходных данных двух различных запусков примера приложения ParallelTasks. Показанные здесь длительности заданий не учитывают время создания пула, так как каждое задание было отправлено в ранее созданный пул, в котором вычислительные узлы находились в состоянии Бездействие во время отправки.

Первый запуск примера приложения показывает, что с одним узлом в пуле и с одной задачей на узел (настройка по умолчанию) на выполнение задания потребовалось более 30 минут.

Nodes: 1
Node size: large
Task slots per node: 1
Max slots per task: 1
Tasks: 32
Duration: 00:30:01.4638023

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

Nodes: 1
Node size: large
Task slots per node: 4
Max slots per task: 1
Tasks: 32
Duration: 00:08:48.2423500

Дальнейшие шаги

  • Испытайте тепловую карту Batch Explorer. Batch Explorer — это бесплатный автономный клиентский инструмент с множеством функций для создания, отладки и мониторинга приложений пакетной службы Azure. При выполнении примера приложения ParallelTasks компонент "Batch Explorer Heat Map" (Тепловая карта Batch Explorer) позволяет наглядно представить параллельное выполнение задач на каждом узле.
  • Изучите примеры пакетной службы Azure на GitHub.
  • Узнайте больше о зависимостях задач пакетной службы.