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

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

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

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

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

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

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

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

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

Примечание

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

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

Совет

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

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

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

С помощью свойства 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": "20.04-lts"
    },
    "nodeAgentSKUId": "batch.node.ubuntu 20.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

Дальнейшие действия